2025-11-08 21:56:57 -08:00
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
|
|
|
|
import { NFL_14, getScoringRoundType } from "~/lib/bracket-templates";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* NFL 14 Bracket Unit Tests - Phase 2.9
|
|
|
|
|
|
*
|
|
|
|
|
|
* Tests the NFL 14 tournament template structure:
|
|
|
|
|
|
* - NFL 14: Wild Card (12 teams) + 2 bye teams → Divisional → Championship → Super Bowl
|
|
|
|
|
|
*/
|
|
|
|
|
|
describe("NFL 14 Bracket Template - Phase 2.9", () => {
|
|
|
|
|
|
describe("Template Structure", () => {
|
|
|
|
|
|
it("has correct total teams", () => {
|
|
|
|
|
|
expect(NFL_14.totalTeams).toBe(14);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("has all 4 rounds", () => {
|
|
|
|
|
|
expect(NFL_14.rounds).toHaveLength(4);
|
|
|
|
|
|
expect(NFL_14.rounds[0].name).toBe("Wild Card");
|
|
|
|
|
|
expect(NFL_14.rounds[1].name).toBe("Divisional");
|
|
|
|
|
|
expect(NFL_14.rounds[2].name).toBe("Conference Championship");
|
|
|
|
|
|
expect(NFL_14.rounds[3].name).toBe("Super Bowl");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("has correct match counts per round", () => {
|
|
|
|
|
|
expect(NFL_14.rounds[0].matchCount).toBe(6); // Wild Card: 6 games (12 teams)
|
|
|
|
|
|
expect(NFL_14.rounds[1].matchCount).toBe(4); // Divisional: 4 games
|
|
|
|
|
|
expect(NFL_14.rounds[2].matchCount).toBe(2); // Conference Championship: 2 games
|
|
|
|
|
|
expect(NFL_14.rounds[3].matchCount).toBe(1); // Super Bowl: 1 game
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("has correct round feeding structure", () => {
|
|
|
|
|
|
expect(NFL_14.rounds[0].feedsInto).toBe("Divisional");
|
|
|
|
|
|
expect(NFL_14.rounds[1].feedsInto).toBe("Conference Championship");
|
|
|
|
|
|
expect(NFL_14.rounds[2].feedsInto).toBe("Super Bowl");
|
|
|
|
|
|
expect(NFL_14.rounds[3].feedsInto).toBeNull();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe("Scoring Configuration", () => {
|
|
|
|
|
|
it("marks scoring to start at Divisional", () => {
|
|
|
|
|
|
expect(NFL_14.scoringStartsAtRound).toBe("Divisional");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("marks Wild Card as non-scoring", () => {
|
|
|
|
|
|
const wildCard = NFL_14.rounds.find((r) => r.name === "Wild Card");
|
|
|
|
|
|
expect(wildCard?.isScoring).toBe(false);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("marks Divisional as scoring (Quarterfinals)", () => {
|
|
|
|
|
|
const divisional = NFL_14.rounds.find((r) => r.name === "Divisional");
|
|
|
|
|
|
expect(divisional?.isScoring).toBe(true);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("marks Conference Championship as scoring (Semifinals)", () => {
|
|
|
|
|
|
const championship = NFL_14.rounds.find((r) => r.name === "Conference Championship");
|
|
|
|
|
|
expect(championship?.isScoring).toBe(true);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("marks Super Bowl as scoring (Finals)", () => {
|
|
|
|
|
|
const superBowl = NFL_14.rounds.find((r) => r.name === "Super Bowl");
|
|
|
|
|
|
expect(superBowl?.isScoring).toBe(true);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("has exactly 3 scoring rounds (Divisional, Championship, Super Bowl)", () => {
|
|
|
|
|
|
const scoringRounds = NFL_14.rounds.filter((r) => r.isScoring);
|
|
|
|
|
|
expect(scoringRounds).toHaveLength(3);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("has exactly 1 non-scoring round (Wild Card)", () => {
|
|
|
|
|
|
const nonScoringRounds = NFL_14.rounds.filter((r) => !r.isScoring);
|
|
|
|
|
|
expect(nonScoringRounds).toHaveLength(1);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe("Scoring Round Types", () => {
|
|
|
|
|
|
it("identifies Divisional as quarterfinals", () => {
|
|
|
|
|
|
expect(getScoringRoundType("Divisional", NFL_14)).toBe("quarterfinals");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("identifies Conference Championship as semifinals", () => {
|
|
|
|
|
|
expect(getScoringRoundType("Conference Championship", NFL_14)).toBe("semifinals");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("identifies Super Bowl as finals", () => {
|
|
|
|
|
|
expect(getScoringRoundType("Super Bowl", NFL_14)).toBe("finals");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("returns null for non-scoring rounds", () => {
|
|
|
|
|
|
expect(getScoringRoundType("Wild Card", NFL_14)).toBeNull();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe("Bye Week Logic", () => {
|
|
|
|
|
|
it("verifies Wild Card has 6 matches for 12 teams (2 teams get byes)", () => {
|
|
|
|
|
|
const wildCard = NFL_14.rounds.find((r) => r.name === "Wild Card");
|
|
|
|
|
|
expect(wildCard?.matchCount).toBe(6); // 6 matches × 2 teams = 12 teams
|
|
|
|
|
|
|
|
|
|
|
|
// Total teams (14) - Wild Card teams (12) = 2 bye teams
|
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
|
|
|
|
const byeTeams = NFL_14.totalTeams - ((wildCard?.matchCount ?? 0) * 2);
|
2025-11-08 21:56:57 -08:00
|
|
|
|
expect(byeTeams).toBe(2);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("verifies Divisional round can accommodate 8 teams (6 Wild Card winners + 2 bye teams)", () => {
|
|
|
|
|
|
const wildCard = NFL_14.rounds.find((r) => r.name === "Wild Card");
|
|
|
|
|
|
const divisional = NFL_14.rounds.find((r) => r.name === "Divisional");
|
|
|
|
|
|
|
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
|
|
|
|
const wildCardWinners = wildCard?.matchCount ?? 0; // 6 winners
|
2025-11-08 21:56:57 -08:00
|
|
|
|
const byeTeams = 2;
|
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
|
|
|
|
const divisionalTeams = (divisional?.matchCount ?? 0) * 2; // 4 matches × 2 = 8 teams
|
2025-11-08 21:56:57 -08:00
|
|
|
|
|
|
|
|
|
|
expect(wildCardWinners + byeTeams).toBe(divisionalTeams);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe("Tournament Math", () => {
|
|
|
|
|
|
it("has correct number of total matches", () => {
|
|
|
|
|
|
const totalMatches = NFL_14.rounds.reduce((sum, round) => sum + round.matchCount, 0);
|
|
|
|
|
|
expect(totalMatches).toBe(13); // 6 + 4 + 2 + 1
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("eliminates 6 teams in Wild Card", () => {
|
|
|
|
|
|
const wildCard = NFL_14.rounds.find((r) => r.name === "Wild Card");
|
|
|
|
|
|
expect(wildCard?.matchCount).toBe(6); // 6 losers
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("has 8 teams in Divisional (top 8 scoring)", () => {
|
|
|
|
|
|
const divisional = NFL_14.rounds.find((r) => r.name === "Divisional");
|
|
|
|
|
|
expect(divisional?.matchCount).toBe(4); // 4 matches = 8 teams
|
|
|
|
|
|
expect(divisional?.isScoring).toBe(true);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("verifies only 8 teams score fantasy points (Divisional and beyond)", () => {
|
|
|
|
|
|
const divisional = NFL_14.rounds.find((r) => r.name === "Divisional");
|
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
|
|
|
|
const divisionalTeams = (divisional?.matchCount ?? 0) * 2; // 8 teams
|
2025-11-08 21:56:57 -08:00
|
|
|
|
|
|
|
|
|
|
expect(divisionalTeams).toBe(8); // Matches requirement from Q18
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|