Skip to content

Commit b400df6

Browse files
authored
Merge branch 'main' into fix/issue-2797
2 parents cd47b5f + ea82434 commit b400df6

42 files changed

Lines changed: 2577 additions & 62 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,3 +586,5 @@ Thanks to all contributors who have helped make CommitPulse better!
586586
</a>
587587

588588
<sub>View the [full contributor list →](https://github.com/JhaSourav07/commitpulse/graphs/contributors)</sub>
589+
590+
test commit for PR creation
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/* ==========================================================================
2+
* COMPONENT LAYER — RESPONSIVE MULTI-DEVICE VIEWPORT BOUNDARIES (VARIATION 7)
3+
* ========================================================================== */
4+
5+
import React from 'react';
6+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7+
import { render, screen } from '@testing-library/react';
8+
9+
const MockComparePage = ({ viewportWidth }: { viewportWidth: number }) => {
10+
const isMobile = viewportWidth <= 768;
11+
return (
12+
<div
13+
style={{
14+
display: 'flex',
15+
flexDirection: isMobile ? 'column' : 'row',
16+
width: '100%',
17+
maxWidth: '1200px',
18+
}}
19+
data-testid="compare-container"
20+
>
21+
<header
22+
style={{ width: '100%', padding: isMobile ? '10px' : '20px' }}
23+
data-testid="nav-header"
24+
>
25+
<span data-testid="nav-scale">{isMobile ? 'Mobile Nav' : 'Desktop Navbar'}</span>
26+
</header>
27+
28+
<div
29+
style={{ flex: 1, minWidth: isMobile ? '100%' : '50%' }}
30+
data-testid="column-user-1"
31+
className={isMobile ? 'w-full block' : 'w-1/2 inline-block'}
32+
>
33+
User 1 Stats Card
34+
</div>
35+
<div
36+
style={{ flex: 1, minWidth: isMobile ? '100%' : '50%' }}
37+
data-testid="column-user-2"
38+
className={isMobile ? 'w-full block' : 'w-1/2 inline-block'}
39+
>
40+
User 2 Stats Card
41+
</div>
42+
43+
{isMobile && (
44+
<button data-testid="mobile-toggle" data-state="active">
45+
Mobile Action View
46+
</button>
47+
)}
48+
</div>
49+
);
50+
};
51+
52+
describe('ComparePage — Responsive Viewport Layout Bounds (Variation 7)', () => {
53+
const originalInnerWidth = window.innerWidth;
54+
55+
const setViewportWidth = (width: number) => {
56+
Object.defineProperty(window, 'innerWidth', {
57+
writable: true,
58+
configurable: true,
59+
value: width,
60+
});
61+
window.dispatchEvent(new Event('resize'));
62+
};
63+
64+
afterEach(() => {
65+
setViewportWidth(originalInnerWidth);
66+
vi.restoreAllMocks();
67+
});
68+
69+
// 1. Mock standard mobile-width media coordinates (e.g. 375px wide viewports)
70+
it('correctly adapts layout constraints to standard mobile coordinates at 375px', () => {
71+
setViewportWidth(375);
72+
render(<MockComparePage viewportWidth={375} />);
73+
74+
const container = screen.getByTestId('compare-container');
75+
expect(window.innerWidth).toBe(375);
76+
expect(container).toBeDefined();
77+
});
78+
79+
it('forces columns to reflow into a vertical stacked flex alignment on narrow device screens', () => {
80+
setViewportWidth(375);
81+
render(<MockComparePage viewportWidth={375} />);
82+
83+
const container = screen.getByTestId('compare-container');
84+
expect(container.style.flexDirection).toBe('column');
85+
});
86+
87+
it('avoids absolute hardcoded pixel width definitions on columns to prevent layout truncation clipping', () => {
88+
setViewportWidth(375);
89+
render(<MockComparePage viewportWidth={375} />);
90+
91+
const col1 = screen.getByTestId('column-user-1');
92+
const col2 = screen.getByTestId('column-user-2');
93+
94+
expect(col1.style.minWidth).toBe('100%');
95+
expect(col2.style.minWidth).toBe('100%');
96+
expect(col1.style.minWidth).not.toBe('600px');
97+
});
98+
99+
it('scales down secondary structural navigation components cleanly on tight breakpoints', () => {
100+
setViewportWidth(375);
101+
render(<MockComparePage viewportWidth={375} />);
102+
103+
const header = screen.getByTestId('nav-header');
104+
const navText = screen.getByTestId('nav-scale');
105+
106+
expect(header.style.padding).toBe('10px');
107+
expect(navText.textContent).toBe('Mobile Nav');
108+
});
109+
110+
it('activates and renders mobile-specific control toggles under small viewport modes exclusively', () => {
111+
setViewportWidth(375);
112+
render(<MockComparePage viewportWidth={375} />);
113+
114+
const mobileToggle = screen.getByTestId('mobile-toggle');
115+
expect(mobileToggle).toBeDefined();
116+
expect(mobileToggle.getAttribute('data-state')).toBe('active');
117+
});
118+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { render } from '@testing-library/react';
2+
import { describe, expect, expectTypeOf, it, vi } from 'vitest';
3+
import ScrollRestoration from './ScrollRestoration';
4+
5+
vi.mock('next/navigation', () => ({
6+
usePathname: () => '/contributors',
7+
}));
8+
9+
type ScrollRestorationComponent = typeof ScrollRestoration;
10+
type ScrollRestorationProps = Parameters<ScrollRestorationComponent>;
11+
12+
function validateScrollStorageKey(key: string) {
13+
return /^scroll-position-\/.+/.test(key);
14+
}
15+
16+
describe('ScrollRestoration type compiler validation', () => {
17+
it('exports a callable React component with no required props', () => {
18+
expectTypeOf(ScrollRestoration).toBeFunction();
19+
expectTypeOf<ScrollRestorationProps>().toEqualTypeOf<[]>();
20+
});
21+
22+
it('renders without visible DOM output', () => {
23+
const { container } = render(<ScrollRestoration />);
24+
25+
expect(container.textContent).toBe('');
26+
expect(container.firstElementChild).toBeNull();
27+
});
28+
29+
it('blocks invalid prop parameters at compile time', () => {
30+
expectTypeOf<ScrollRestorationProps>().not.toEqualTypeOf<[{ pathname: string }]>();
31+
});
32+
33+
it('accepts optional pathname-like values in schema helper checks', () => {
34+
type OptionalPathname = string | undefined;
35+
36+
const optionalPathname: OptionalPathname = '/contributors';
37+
38+
expectTypeOf<OptionalPathname>().toEqualTypeOf<string | undefined>();
39+
40+
expect(validateScrollStorageKey(`scroll-position-${optionalPathname}`)).toBe(true);
41+
});
42+
43+
it('returns strict validation reports for scroll storage key constraints', () => {
44+
expect(validateScrollStorageKey('scroll-position-/contributors')).toBe(true);
45+
expect(validateScrollStorageKey('scroll-position-')).toBe(false);
46+
expect(validateScrollStorageKey('contributors')).toBe(false);
47+
expect(validateScrollStorageKey('')).toBe(false);
48+
});
49+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { describe, expectTypeOf, it } from 'vitest';
2+
import { createAnimation, type AnimationStart, type AnimationVariant } from './theme-switch';
3+
4+
describe('ThemeSwitch Type Compiler Validation', () => {
5+
it('validates AnimationVariant union values', () => {
6+
expectTypeOf<AnimationVariant>().toEqualTypeOf<
7+
'circle' | 'rectangle' | 'gif' | 'polygon' | 'circle-blur'
8+
>();
9+
});
10+
11+
it('validates AnimationStart type', () => {
12+
expectTypeOf<AnimationStart>().toBeString();
13+
});
14+
15+
it('ensures createAnimation returns required structure', () => {
16+
const animation = createAnimation('circle');
17+
18+
expectTypeOf(animation.name).toBeString();
19+
expectTypeOf(animation.css).toBeString();
20+
});
21+
22+
it('accepts optional parameters without compile errors', () => {
23+
const animation = createAnimation('circle');
24+
25+
expectTypeOf(animation.name).toBeString();
26+
expectTypeOf(animation.css).toBeString();
27+
});
28+
29+
it('accepts valid exported types in createAnimation', () => {
30+
const variant: AnimationVariant = 'circle';
31+
const start: AnimationStart = 'center';
32+
33+
const animation = createAnimation(variant, start);
34+
35+
expectTypeOf(animation.name).toBeString();
36+
expectTypeOf(animation.css).toBeString();
37+
});
38+
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { render, screen } from '@testing-library/react';
2+
import { describe, expect, it } from 'vitest';
3+
import Loading from './loading';
4+
5+
function hasClasses(element: Element | null, classes: string[]) {
6+
expect(element).not.toBeNull();
7+
8+
for (const className of classes) {
9+
expect(element!.classList.contains(className)).toBe(true);
10+
}
11+
}
12+
13+
describe('Contributors loading accessibility', () => {
14+
it('exposes the loading state through a screen-reader status region', () => {
15+
render(<Loading />);
16+
17+
const status = screen.getByRole('status');
18+
19+
expect(status.getAttribute('aria-live')).toBe('polite');
20+
expect(status.textContent).toContain('Loading the collective...');
21+
expect(status.textContent).toContain('Fetching contributor data from GitHub');
22+
});
23+
24+
it('keeps descriptive loading text available to assistive technologies', () => {
25+
render(<Loading />);
26+
27+
expect(screen.getByText('Loading the collective...')).toBeTruthy();
28+
expect(screen.getByText('Fetching contributor data from GitHub')).toBeTruthy();
29+
});
30+
31+
it('does not expose decorative spinner elements as interactive controls', () => {
32+
render(<Loading />);
33+
34+
expect(screen.queryByRole('button')).toBeNull();
35+
expect(screen.queryByRole('link')).toBeNull();
36+
expect(screen.queryByRole('textbox')).toBeNull();
37+
});
38+
39+
it('preserves visual focus-friendly layout without hidden foreground text', () => {
40+
render(<Loading />);
41+
42+
const status = screen.getByRole('status');
43+
const page = status.parentElement;
44+
45+
hasClasses(page, [
46+
'flex',
47+
'min-h-screen',
48+
'items-center',
49+
'justify-center',
50+
'bg-[#050505]',
51+
'text-white',
52+
]);
53+
54+
expect(status.classList.contains('sr-only')).toBe(false);
55+
expect(status.classList.contains('hidden')).toBe(false);
56+
expect(status.classList.contains('text-transparent')).toBe(false);
57+
});
58+
59+
it('keeps DOM reading order logical for keyboard and screen-reader traversal', () => {
60+
render(<Loading />);
61+
62+
const status = screen.getByRole('status');
63+
const children = Array.from(status.children);
64+
65+
expect(children.length).toBeGreaterThanOrEqual(3);
66+
expect(children[0].tagName.toLowerCase()).toBe('div');
67+
expect(children[1].textContent).toBe('Loading the collective...');
68+
expect(children[2].textContent).toBe('Fetching contributor data from GitHub');
69+
});
70+
});
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { render, screen } from '@testing-library/react';
2+
import { describe, expect, it } from 'vitest';
3+
import Loading from './loading';
4+
5+
function hasClasses(element: Element | null, classes: string[]) {
6+
expect(element).not.toBeNull();
7+
8+
for (const className of classes) {
9+
expect(element!.classList.contains(className)).toBe(true);
10+
}
11+
}
12+
13+
describe('Contributors loading empty fallback', () => {
14+
it('renders safely without props or input data', () => {
15+
expect(() => render(<Loading />)).not.toThrow();
16+
});
17+
18+
it('shows a clear fallback loading state', () => {
19+
render(<Loading />);
20+
21+
expect(screen.getByRole('status')).toBeTruthy();
22+
expect(screen.getByText('Loading the collective...')).toBeTruthy();
23+
expect(screen.getByText('Fetching contributor data from GitHub')).toBeTruthy();
24+
});
25+
26+
it('keeps accessible fallback markers available', () => {
27+
render(<Loading />);
28+
29+
const status = screen.getByRole('status');
30+
31+
expect(status.getAttribute('aria-live')).toBe('polite');
32+
expect(status.children.length).toBeGreaterThanOrEqual(2);
33+
});
34+
35+
it('maintains default fallback layout styles', () => {
36+
render(<Loading />);
37+
38+
const status = screen.getByRole('status');
39+
const page = status.parentElement;
40+
41+
hasClasses(page, [
42+
'flex',
43+
'min-h-screen',
44+
'items-center',
45+
'justify-center',
46+
'bg-[#050505]',
47+
'text-white',
48+
]);
49+
50+
hasClasses(status, ['flex', 'flex-col', 'items-center', 'gap-6']);
51+
});
52+
53+
it('renders fallback spinner structure without hydration-sensitive content', () => {
54+
render(<Loading />);
55+
56+
const status = screen.getByRole('status');
57+
const spinnerWrapper = status.firstElementChild;
58+
const spinner = spinnerWrapper?.firstElementChild ?? null;
59+
const glowOverlay = spinnerWrapper?.children.item(1) ?? null;
60+
61+
hasClasses(spinnerWrapper, ['relative']);
62+
63+
hasClasses(spinner, [
64+
'h-16',
65+
'w-16',
66+
'animate-spin',
67+
'rounded-full',
68+
'border-2',
69+
'border-white/10',
70+
'border-t-cyan-400',
71+
]);
72+
73+
hasClasses(glowOverlay, [
74+
'absolute',
75+
'inset-0',
76+
'h-16',
77+
'w-16',
78+
'rounded-full',
79+
'bg-cyan-400/20',
80+
'blur-xl',
81+
'animate-pulse',
82+
]);
83+
84+
expect(status.classList.contains('overflow-hidden')).toBe(false);
85+
expect(status.classList.contains('text-transparent')).toBe(false);
86+
});
87+
});

app/contributors/page.empty-fallback.test.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
/* eslint-disable @typescript-eslint/no-unused-vars */
3+
/* eslint-disable @next/next/no-img-element, jsx-a11y/alt-text */
24
import { beforeEach, describe, expect, it, vi } from 'vitest';
35
import { render, screen } from '@testing-library/react';
46
import ContributorsPage from './page';
57

68
vi.mock('next/image', () => ({
79
__esModule: true,
8-
default: (props: any) => <img {...props} />,
10+
default: (props: any) => {
11+
const { fill, ...rest } = props || {};
12+
return <img {...rest} />;
13+
},
914
}));
1015

1116
vi.mock('next/link', () => ({

0 commit comments

Comments
 (0)