2025-10-21 12:47:11 -07:00
|
|
|
import { describe, it, expect } from 'vitest';
|
2025-10-24 21:20:19 -07:00
|
|
|
import { cn } from '~/lib/utils';
|
2025-10-21 12:47:11 -07:00
|
|
|
|
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
|
|
|
function formatTime(seconds: number | undefined): string {
|
|
|
|
|
if (seconds === undefined) return '--:--';
|
|
|
|
|
const mins = Math.floor(seconds / 60);
|
|
|
|
|
const secs = seconds % 60;
|
|
|
|
|
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isValidLeagueName(name: string): boolean {
|
|
|
|
|
return name.trim().length >= 3 && name.trim().length <= 50;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type DraftSpeed = 'fast' | 'standard' | 'slow' | 'very-slow';
|
|
|
|
|
|
|
|
|
|
function getDraftTimes(speed: DraftSpeed): { initial: number; increment: number } {
|
|
|
|
|
switch (speed) {
|
|
|
|
|
case 'fast':
|
|
|
|
|
return { initial: 60, increment: 10 };
|
|
|
|
|
case 'standard':
|
|
|
|
|
return { initial: 120, increment: 15 };
|
|
|
|
|
case 'slow':
|
|
|
|
|
return { initial: 28800, increment: 3600 };
|
|
|
|
|
case 'very-slow':
|
|
|
|
|
return { initial: 43200, increment: 3600 };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-21 12:47:11 -07:00
|
|
|
describe('Utils', () => {
|
|
|
|
|
describe('cn (className merger)', () => {
|
|
|
|
|
it('should merge class names', () => {
|
|
|
|
|
const result = cn('foo', 'bar');
|
|
|
|
|
expect(result).toContain('foo');
|
|
|
|
|
expect(result).toContain('bar');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle conditional classes', () => {
|
2025-10-21 12:57:03 -07:00
|
|
|
const condition = false;
|
|
|
|
|
const result = cn('foo', condition && 'bar', 'baz');
|
2025-10-21 12:47:11 -07:00
|
|
|
expect(result).toContain('foo');
|
|
|
|
|
expect(result).toContain('baz');
|
|
|
|
|
expect(result).not.toContain('bar');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle undefined and null', () => {
|
|
|
|
|
const result = cn('foo', undefined, null, 'bar');
|
|
|
|
|
expect(result).toContain('foo');
|
|
|
|
|
expect(result).toContain('bar');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should merge tailwind classes correctly', () => {
|
|
|
|
|
const result = cn('px-2 py-1', 'px-4');
|
|
|
|
|
// Should prefer the last px value
|
|
|
|
|
expect(result).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Draft Time Formatting', () => {
|
|
|
|
|
it('should format time correctly', () => {
|
|
|
|
|
expect(formatTime(120)).toBe('2:00');
|
|
|
|
|
expect(formatTime(90)).toBe('1:30');
|
|
|
|
|
expect(formatTime(0)).toBe('0:00');
|
|
|
|
|
expect(formatTime(3661)).toBe('61:01');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle undefined', () => {
|
|
|
|
|
expect(formatTime(undefined)).toBe('--:--');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should pad seconds with zero', () => {
|
|
|
|
|
expect(formatTime(65)).toBe('1:05');
|
|
|
|
|
expect(formatTime(5)).toBe('0:05');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('League Name Validation', () => {
|
|
|
|
|
it('should accept valid league names', () => {
|
|
|
|
|
expect(isValidLeagueName('My League')).toBe(true);
|
|
|
|
|
expect(isValidLeagueName('Test League 2025')).toBe(true);
|
|
|
|
|
expect(isValidLeagueName('ABC')).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should reject names that are too short', () => {
|
|
|
|
|
expect(isValidLeagueName('ab')).toBe(false);
|
|
|
|
|
expect(isValidLeagueName('a')).toBe(false);
|
|
|
|
|
expect(isValidLeagueName('')).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should reject names that are too long', () => {
|
|
|
|
|
const longName = 'a'.repeat(51);
|
|
|
|
|
expect(isValidLeagueName(longName)).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should trim whitespace before validation', () => {
|
|
|
|
|
expect(isValidLeagueName(' abc ')).toBe(true);
|
|
|
|
|
expect(isValidLeagueName(' ab ')).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Draft Speed Presets', () => {
|
|
|
|
|
it('should return correct times for fast speed', () => {
|
|
|
|
|
const times = getDraftTimes('fast');
|
|
|
|
|
expect(times.initial).toBe(60);
|
|
|
|
|
expect(times.increment).toBe(10);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return correct times for standard speed', () => {
|
|
|
|
|
const times = getDraftTimes('standard');
|
|
|
|
|
expect(times.initial).toBe(120);
|
|
|
|
|
expect(times.increment).toBe(15);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return correct times for slow speed', () => {
|
|
|
|
|
const times = getDraftTimes('slow');
|
|
|
|
|
expect(times.initial).toBe(28800); // 8 hours
|
|
|
|
|
expect(times.increment).toBe(3600); // 1 hour
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return correct times for very-slow speed', () => {
|
|
|
|
|
const times = getDraftTimes('very-slow');
|
|
|
|
|
expect(times.initial).toBe(43200); // 12 hours
|
|
|
|
|
expect(times.increment).toBe(3600); // 1 hour
|
|
|
|
|
});
|
|
|
|
|
});
|