Skip to content

Commit f778bda

Browse files
Merge branch 'main' into docs/cache-getorset-comments
2 parents c6f9083 + eb265c1 commit f778bda

6 files changed

Lines changed: 419 additions & 0 deletions

File tree

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+
});

lib/i18n/languages/ja.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { getLabels, labels } from '../badgeLabels';
3+
4+
const requiredKeys = [
5+
'CURRENT_STREAK',
6+
'ANNUAL_SYNC_TOTAL',
7+
'PEAK_STREAK',
8+
'COMMITS_THIS_MONTH',
9+
'VS_LAST_MONTH',
10+
] as const;
11+
12+
const expectedJapaneseLabels = {
13+
CURRENT_STREAK: '現在のストリーク',
14+
ANNUAL_SYNC_TOTAL: '年間合計',
15+
PEAK_STREAK: '最高ストリーク',
16+
COMMITS_THIS_MONTH: '今月のコミット数',
17+
VS_LAST_MONTH: '先月比',
18+
};
19+
20+
function renderMockBadge(lang: string) {
21+
const badgeLabels = getLabels(lang);
22+
23+
return `
24+
<svg role="img" data-lang="${lang}">
25+
<text>${badgeLabels.CURRENT_STREAK}</text>
26+
<text>${badgeLabels.ANNUAL_SYNC_TOTAL}</text>
27+
<text>${badgeLabels.PEAK_STREAK}</text>
28+
<text>${badgeLabels.COMMITS_THIS_MONTH}</text>
29+
<text>${badgeLabels.VS_LAST_MONTH}</text>
30+
</svg>
31+
`;
32+
}
33+
34+
describe('Japanese badge labels', () => {
35+
it('includes the ja language key in the labels dictionary', () => {
36+
expect(labels.ja).toBeDefined();
37+
});
38+
39+
it('returns the exact Japanese translation mapping through getLabels', () => {
40+
expect(getLabels('ja')).toEqual(expectedJapaneseLabels);
41+
});
42+
43+
it('returns the Japanese mapping when lang code casing differs', () => {
44+
expect(getLabels('JA')).toEqual(expectedJapaneseLabels);
45+
});
46+
47+
it('sets every required Japanese label to a non-empty string', () => {
48+
const japaneseLabels = getLabels('ja');
49+
50+
for (const key of requiredKeys) {
51+
expect(typeof japaneseLabels[key]).toBe('string');
52+
expect(japaneseLabels[key].trim().length).toBeGreaterThan(0);
53+
}
54+
});
55+
56+
it('renders a mock SVG with Japanese translated labels', () => {
57+
const svg = renderMockBadge('ja');
58+
59+
expect(svg).toContain('data-lang="ja"');
60+
61+
for (const value of Object.values(expectedJapaneseLabels)) {
62+
expect(svg).toContain(value);
63+
}
64+
});
65+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// lib/resume-parser.timezone-boundaries.test.ts
2+
3+
import { describe, expect, it } from 'vitest';
4+
import { parseResume } from './resume-parser';
5+
6+
describe('Resume Parser Timezone Boundaries', () => {
7+
it('parses resume data consistently with UTC date strings', async () => {
8+
const resume = `
9+
John Doe
10+
john@example.com
11+
12+
Experience
13+
Software Engineer 2020-2024 UTC
14+
`;
15+
16+
const result = await parseResume(Buffer.from(resume), 'application/pdf');
17+
18+
expect(result).toBeDefined();
19+
expect(typeof result.name).toBe('string');
20+
});
21+
22+
it('parses resume data consistently with EST date strings', async () => {
23+
const resume = `
24+
John Doe
25+
john@example.com
26+
27+
Experience
28+
Software Engineer 2020-2024 EST
29+
`;
30+
31+
const result = await parseResume(Buffer.from(resume), 'application/pdf');
32+
33+
expect(result).toBeDefined();
34+
expect(result.experience).toBeDefined();
35+
});
36+
37+
it('parses resume data consistently with IST date strings', async () => {
38+
const resume = `
39+
John Doe
40+
john@example.com
41+
42+
Education
43+
University Degree 2019-2023 IST
44+
`;
45+
46+
const result = await parseResume(Buffer.from(resume), 'application/pdf');
47+
48+
expect(result).toBeDefined();
49+
expect(result.education).toBeDefined();
50+
});
51+
52+
it('handles leap-year date references without failures', async () => {
53+
const resume = `
54+
John Doe
55+
john@example.com
56+
57+
Experience
58+
Project Lead Feb 29 2024
59+
`;
60+
61+
const result = await parseResume(Buffer.from(resume), 'application/pdf');
62+
63+
expect(result).toBeDefined();
64+
});
65+
66+
it('handles daylight-saving and boundary date text safely', async () => {
67+
const resume = `
68+
John Doe
69+
john@example.com
70+
71+
Experience
72+
Engineer March 10 2024 DST
73+
`;
74+
75+
const result = await parseResume(Buffer.from(resume), 'application/pdf');
76+
77+
expect(result).toBeDefined();
78+
expect(result.email).toBe('john@example.com');
79+
});
80+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// lib/resume-parser.type-compiler.test.ts
2+
3+
import { describe, expectTypeOf, it } from 'vitest';
4+
import { parseResume, ALLOWED_MIME_TYPES, MAX_FILE_SIZE } from './resume-parser';
5+
6+
import type { ParsedResume, Education, Experience } from '@/types/student';
7+
8+
describe('Resume Parser Type Compiler Validation', () => {
9+
it('returns a Promise of ParsedResume', () => {
10+
expectTypeOf(parseResume).returns.toEqualTypeOf<Promise<ParsedResume>>();
11+
});
12+
13+
it('accepts expected parseResume parameter types', () => {
14+
expectTypeOf(parseResume).parameters.toEqualTypeOf<[Buffer, string]>();
15+
});
16+
17+
it('validates ParsedResume structure', () => {
18+
expectTypeOf<ParsedResume>().toMatchTypeOf({
19+
name: '',
20+
email: '',
21+
phone: '',
22+
skills: [] as string[],
23+
education: [] as Education[],
24+
experience: [] as Experience[],
25+
});
26+
});
27+
28+
it('enforces allowed mime types array typing', () => {
29+
expectTypeOf(ALLOWED_MIME_TYPES).toEqualTypeOf<string[]>();
30+
});
31+
32+
it('enforces MAX_FILE_SIZE numeric typing', () => {
33+
expectTypeOf(MAX_FILE_SIZE).toEqualTypeOf<number>();
34+
});
35+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { describe, it, expect, beforeEach } from 'vitest';
2+
import { QuotaMonitor } from './quota-monitor';
3+
4+
describe('QuotaMonitor', () => {
5+
let monitor: QuotaMonitor;
6+
7+
beforeEach(() => {
8+
monitor = QuotaMonitor.getInstance();
9+
monitor.reset();
10+
});
11+
12+
it('parses X-RateLimit headers correctly', () => {
13+
monitor.updateQuotaFromHeaders({
14+
'x-ratelimit-limit': '5000',
15+
'x-ratelimit-remaining': '4200',
16+
'x-ratelimit-reset': '1710000000',
17+
});
18+
19+
const quota = monitor.getQuota();
20+
21+
expect(quota.limit).toBe(5000);
22+
expect(quota.remaining).toBe(4200);
23+
});
24+
25+
it('calculates remaining API credits correctly', () => {
26+
monitor.setQuota(5000, 1234, Date.now());
27+
28+
const quota = monitor.getQuota();
29+
30+
expect(quota.limit).toBe(5000);
31+
expect(quota.remaining).toBe(1234);
32+
});
33+
34+
it('flags quota as low when remaining credits are below 10%', () => {
35+
monitor.setQuota(5000, 499, Date.now());
36+
37+
expect(monitor.isQuotaLow()).toBe(true);
38+
39+
monitor.setQuota(5000, 500, Date.now());
40+
41+
expect(monitor.isQuotaLow()).toBe(false);
42+
});
43+
44+
it('tracks refresh operations correctly', () => {
45+
monitor.incrementRefreshCount();
46+
monitor.incrementRefreshCount();
47+
monitor.incrementRefreshCount();
48+
49+
expect(monitor.getQuota().totalRefreshes).toBe(3);
50+
});
51+
52+
it('parses reset window timestamps into milliseconds', () => {
53+
monitor.updateQuotaFromHeaders({
54+
'x-ratelimit-reset': '1710000000',
55+
});
56+
57+
expect(monitor.getQuota().resetTime).toBe(1710000000 * 1000);
58+
});
59+
});

0 commit comments

Comments
 (0)