123 lines
3.6 KiB
TypeScript
123 lines
3.6 KiB
TypeScript
|
|
import { describe, it, expect } from "vitest";
|
||
|
|
import { computeCoronaStates } from "../corona-states";
|
||
|
|
|
||
|
|
const RULES = {
|
||
|
|
pointsFor1st: 100, pointsFor2nd: 70, pointsFor3rd: 50, pointsFor4th: 40,
|
||
|
|
pointsFor5th: 25, pointsFor6th: 25, pointsFor7th: 15, pointsFor8th: 15,
|
||
|
|
};
|
||
|
|
|
||
|
|
function pick(id: string, scoringPattern: string | null, sportsSeasonId = "ss-1") {
|
||
|
|
return { participant: { id, sportsSeasonId }, scoringPattern };
|
||
|
|
}
|
||
|
|
|
||
|
|
function results(
|
||
|
|
rows: Array<{ participantId: string; finalPosition: number | null; isPartialScore?: boolean }>
|
||
|
|
) {
|
||
|
|
return new Map(
|
||
|
|
rows.map((r) => [r.participantId, { isPartialScore: false, ...r }])
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
describe("computeCoronaStates", () => {
|
||
|
|
const noTies = new Map<string, Map<number, number>>();
|
||
|
|
|
||
|
|
it("splits points for a tied qualifying_points pick", () => {
|
||
|
|
// Rahm tied for 8th. 9th is outside the scoring range and contributes 0,
|
||
|
|
// so (15 + 0) / 2 = 7.5 → 8. Before the shared helper this path awarded the
|
||
|
|
// full 15 while the standings awarded the split — the reported bug.
|
||
|
|
const states = computeCoronaStates(
|
||
|
|
[pick("rahm", "qualifying_points")],
|
||
|
|
results([{ participantId: "rahm", finalPosition: 8 }]),
|
||
|
|
new Map(),
|
||
|
|
RULES,
|
||
|
|
RULES.pointsFor1st,
|
||
|
|
new Map([["ss-1", new Map([[8, 2]])]])
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(states.rahm).toEqual({
|
||
|
|
type: "scored",
|
||
|
|
points: 8,
|
||
|
|
brightness: 8 / 100,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("awards the full value when the placement is untied", () => {
|
||
|
|
const states = computeCoronaStates(
|
||
|
|
[pick("rahm", "qualifying_points")],
|
||
|
|
results([{ participantId: "rahm", finalPosition: 8 }]),
|
||
|
|
new Map(),
|
||
|
|
RULES,
|
||
|
|
RULES.pointsFor1st,
|
||
|
|
noTies
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(states.rahm).toMatchObject({ type: "scored", points: 15 });
|
||
|
|
});
|
||
|
|
|
||
|
|
it("still averages bracket tiers via the bracket template", () => {
|
||
|
|
const states = computeCoronaStates(
|
||
|
|
[pick("team-a", "playoff_bracket")],
|
||
|
|
results([{ participantId: "team-a", finalPosition: 5 }]),
|
||
|
|
new Map([["ss-1", null]]),
|
||
|
|
RULES,
|
||
|
|
RULES.pointsFor1st,
|
||
|
|
noTies
|
||
|
|
);
|
||
|
|
|
||
|
|
// Four QF losers share 5th-8th: (25 + 25 + 15 + 15) / 4 = 20
|
||
|
|
expect(states["team-a"]).toMatchObject({ type: "scored", points: 20 });
|
||
|
|
});
|
||
|
|
|
||
|
|
it("scores other patterns straight off the placement", () => {
|
||
|
|
const states = computeCoronaStates(
|
||
|
|
[pick("driver", "season_standings")],
|
||
|
|
results([{ participantId: "driver", finalPosition: 2 }]),
|
||
|
|
new Map(),
|
||
|
|
RULES,
|
||
|
|
RULES.pointsFor1st,
|
||
|
|
noTies
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(states.driver).toMatchObject({ type: "scored", points: 70 });
|
||
|
|
});
|
||
|
|
|
||
|
|
it("marks picks with no result as pending", () => {
|
||
|
|
const states = computeCoronaStates(
|
||
|
|
[pick("rahm", "qualifying_points")],
|
||
|
|
results([]),
|
||
|
|
new Map(),
|
||
|
|
RULES,
|
||
|
|
RULES.pointsFor1st,
|
||
|
|
noTies
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(states.rahm).toEqual({ type: "pending" });
|
||
|
|
});
|
||
|
|
|
||
|
|
it("marks a finalized zero placement as eliminated", () => {
|
||
|
|
const states = computeCoronaStates(
|
||
|
|
[pick("rahm", "qualifying_points")],
|
||
|
|
results([{ participantId: "rahm", finalPosition: 0 }]),
|
||
|
|
new Map(),
|
||
|
|
RULES,
|
||
|
|
RULES.pointsFor1st,
|
||
|
|
noTies
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(states.rahm).toEqual({ type: "eliminated", points: 0 });
|
||
|
|
});
|
||
|
|
|
||
|
|
it("keeps brightness within range when a tier exceeds maxPoints", () => {
|
||
|
|
const states = computeCoronaStates(
|
||
|
|
[pick("champ", "season_standings")],
|
||
|
|
results([{ participantId: "champ", finalPosition: 1 }]),
|
||
|
|
new Map(),
|
||
|
|
RULES,
|
||
|
|
10, // deliberately below the 1st-place award
|
||
|
|
noTies
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(states.champ).toMatchObject({ brightness: 1 });
|
||
|
|
});
|
||
|
|
});
|