volleyballsimtypes 0.0.388 → 0.0.389

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,20 +36,28 @@ class Standing {
36
36
  this.totalMatches = this.matchesWon + this.matchesLost;
37
37
  }
38
38
  static sortFn(matches) {
39
+ // A zero denominator (no sets/points lost) is the BEST possible ratio, not the worst. The stored
40
+ // setsRatio/pointsRatio fall back to 0 in that case (and the table shows "MAX"), so we compare an
41
+ // effective ratio here that treats "won with nothing lost" as +Infinity, ranking it highest.
42
+ const effectiveRatio = (won, lost) => lost > 0 ? won / lost : (won > 0 ? Infinity : 0);
39
43
  return (standingA, standingB) => {
40
44
  if (standingB.points > standingA.points)
41
45
  return 1;
42
- else if (standingA.points > standingB.points)
46
+ if (standingA.points > standingB.points)
43
47
  return -1;
44
- else if (standingB.setsRatio > standingA.setsRatio)
48
+ const setRatioA = effectiveRatio(standingA.setsWon, standingA.setsLost);
49
+ const setRatioB = effectiveRatio(standingB.setsWon, standingB.setsLost);
50
+ if (setRatioB > setRatioA)
45
51
  return 1;
46
- else if (standingA.setsRatio > standingB.setsRatio)
52
+ if (setRatioA > setRatioB)
47
53
  return -1;
48
- else if (standingB.pointsRatio > standingA.pointsRatio)
54
+ const pointRatioA = effectiveRatio(standingA.pointsWon, standingA.pointsLost);
55
+ const pointRatioB = effectiveRatio(standingB.pointsWon, standingB.pointsLost);
56
+ if (pointRatioB > pointRatioA)
49
57
  return 1;
50
- else if (standingA.pointsRatio > standingB.pointsRatio)
58
+ if (pointRatioA > pointRatioB)
51
59
  return -1;
52
- else if (matches.length > 0) {
60
+ if (matches.length > 0) {
53
61
  const ab = matches.find((match) => match.homeTeam.id === standingA.teamId && match.awayTeam.id === standingB.teamId);
54
62
  const ba = matches.find((match) => match.awayTeam.id === standingA.teamId && match.homeTeam.id === standingB.teamId);
55
63
  if (ba == null && ab == null)
@@ -66,8 +74,7 @@ class Standing {
66
74
  const b = (ba.homeScore ?? 0) + (ab.awayScore ?? 0);
67
75
  return b - a;
68
76
  }
69
- else
70
- return 0;
77
+ return 0;
71
78
  };
72
79
  }
73
80
  calculateStanding(matches) {
@@ -136,6 +136,18 @@ function makeStanding(teamId, opts = {}) {
136
136
  const sorted = [b, a].sort(standing_1.Standing.sortFn([]));
137
137
  (0, globals_1.expect)(sorted[0].teamId).toBe(idA);
138
138
  });
139
+ (0, globals_1.it)('ranks a team with no sets lost (MAX ratio) above a finite setsRatio when points tie', () => {
140
+ const idA = (0, uuid_1.v4)();
141
+ const idB = (0, uuid_1.v4)();
142
+ // Both 6 points. A swept its matches (won30=2 → setsWon=6, setsLost=0 → ratio is MAX/infinite);
143
+ // B also has 6 points but conceded sets (won31=2 → setsWon=6, setsLost=2 → setsRatio=3).
144
+ // The undefeated-in-sets team must rank FIRST, not last (the divide-by-zero bug ranked it last).
145
+ const a = makeStanding(idA, { won30: 2 });
146
+ const b = makeStanding(idB, { won31: 2 });
147
+ const sorted = [b, a].sort(standing_1.Standing.sortFn([]));
148
+ (0, globals_1.expect)(sorted[0].teamId).toBe(idA);
149
+ (0, globals_1.expect)(sorted[1].teamId).toBe(idB);
150
+ });
139
151
  (0, globals_1.it)('falls back to pointsRatio when points and setsRatio are equal', () => {
140
152
  const idA = (0, uuid_1.v4)();
141
153
  const idB = (0, uuid_1.v4)();
@@ -33,20 +33,28 @@ export class Standing {
33
33
  this.totalMatches = this.matchesWon + this.matchesLost;
34
34
  }
35
35
  static sortFn(matches) {
36
+ // A zero denominator (no sets/points lost) is the BEST possible ratio, not the worst. The stored
37
+ // setsRatio/pointsRatio fall back to 0 in that case (and the table shows "MAX"), so we compare an
38
+ // effective ratio here that treats "won with nothing lost" as +Infinity, ranking it highest.
39
+ const effectiveRatio = (won, lost) => lost > 0 ? won / lost : (won > 0 ? Infinity : 0);
36
40
  return (standingA, standingB) => {
37
41
  if (standingB.points > standingA.points)
38
42
  return 1;
39
- else if (standingA.points > standingB.points)
43
+ if (standingA.points > standingB.points)
40
44
  return -1;
41
- else if (standingB.setsRatio > standingA.setsRatio)
45
+ const setRatioA = effectiveRatio(standingA.setsWon, standingA.setsLost);
46
+ const setRatioB = effectiveRatio(standingB.setsWon, standingB.setsLost);
47
+ if (setRatioB > setRatioA)
42
48
  return 1;
43
- else if (standingA.setsRatio > standingB.setsRatio)
49
+ if (setRatioA > setRatioB)
44
50
  return -1;
45
- else if (standingB.pointsRatio > standingA.pointsRatio)
51
+ const pointRatioA = effectiveRatio(standingA.pointsWon, standingA.pointsLost);
52
+ const pointRatioB = effectiveRatio(standingB.pointsWon, standingB.pointsLost);
53
+ if (pointRatioB > pointRatioA)
46
54
  return 1;
47
- else if (standingA.pointsRatio > standingB.pointsRatio)
55
+ if (pointRatioA > pointRatioB)
48
56
  return -1;
49
- else if (matches.length > 0) {
57
+ if (matches.length > 0) {
50
58
  const ab = matches.find((match) => match.homeTeam.id === standingA.teamId && match.awayTeam.id === standingB.teamId);
51
59
  const ba = matches.find((match) => match.awayTeam.id === standingA.teamId && match.homeTeam.id === standingB.teamId);
52
60
  if (ba == null && ab == null)
@@ -63,8 +71,7 @@ export class Standing {
63
71
  const b = (ba.homeScore ?? 0) + (ab.awayScore ?? 0);
64
72
  return b - a;
65
73
  }
66
- else
67
- return 0;
74
+ return 0;
68
75
  };
69
76
  }
70
77
  calculateStanding(matches) {
@@ -134,6 +134,18 @@ describe('Standing', () => {
134
134
  const sorted = [b, a].sort(Standing.sortFn([]));
135
135
  expect(sorted[0].teamId).toBe(idA);
136
136
  });
137
+ it('ranks a team with no sets lost (MAX ratio) above a finite setsRatio when points tie', () => {
138
+ const idA = uuidv4();
139
+ const idB = uuidv4();
140
+ // Both 6 points. A swept its matches (won30=2 → setsWon=6, setsLost=0 → ratio is MAX/infinite);
141
+ // B also has 6 points but conceded sets (won31=2 → setsWon=6, setsLost=2 → setsRatio=3).
142
+ // The undefeated-in-sets team must rank FIRST, not last (the divide-by-zero bug ranked it last).
143
+ const a = makeStanding(idA, { won30: 2 });
144
+ const b = makeStanding(idB, { won31: 2 });
145
+ const sorted = [b, a].sort(Standing.sortFn([]));
146
+ expect(sorted[0].teamId).toBe(idA);
147
+ expect(sorted[1].teamId).toBe(idB);
148
+ });
137
149
  it('falls back to pointsRatio when points and setsRatio are equal', () => {
138
150
  const idA = uuidv4();
139
151
  const idB = uuidv4();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volleyballsimtypes",
3
- "version": "0.0.388",
3
+ "version": "0.0.389",
4
4
  "description": "vbsim types",
5
5
  "main": "./dist/cjs/src/index.js",
6
6
  "module": "./dist/esm/src/index.js",