volleyballsimtypes 0.0.434 → 0.0.436
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.
|
@@ -92,15 +92,26 @@ function buildPinchServerSubs(pinchServerSubs, roster) {
|
|
|
92
92
|
}
|
|
93
93
|
function transformToObject(model, roster) {
|
|
94
94
|
const lineup = transformToLineup(model.lineup, roster);
|
|
95
|
-
// A second libero only
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
95
|
+
// A second libero is only valid as a RESERVE: it needs a starting libero (LIBERO_ZONE), the L2 player must be
|
|
96
|
+
// on the roster, and it must NOT already occupy the lineup (the 6 starters, the libero zone, or the bench).
|
|
97
|
+
// Edge/legacy data can violate any of these -- the starting libero was removed without clearing the L2, the
|
|
98
|
+
// L2 retired/was dismissed, or the L2 is also the starting libero -- and any such row fails validation on
|
|
99
|
+
// load (SECOND_LIBERO_MUST_BE_RESERVE / REQUIRES_LIBERO) and would crash the whole sim batch. Drop the
|
|
100
|
+
// offending second libero (and, via the gate below, its swap config) so the team still loads and plays
|
|
101
|
+
// without an L2; the next tactics save rewrites it cleanly. roster.find (not findPlayer) so a gone L2 yields
|
|
101
102
|
// undefined instead of throwing.
|
|
103
|
+
const inLineupIds = new Set([
|
|
104
|
+
lineup[service_1.CourtPosition.LEFT_FRONT].id,
|
|
105
|
+
lineup[service_1.CourtPosition.MIDDLE_FRONT].id,
|
|
106
|
+
lineup[service_1.CourtPosition.RIGHT_FRONT].id,
|
|
107
|
+
lineup[service_1.CourtPosition.LEFT_BACK].id,
|
|
108
|
+
lineup[service_1.CourtPosition.MIDDLE_BACK].id,
|
|
109
|
+
lineup[service_1.CourtPosition.RIGHT_BACK].id,
|
|
110
|
+
...(lineup[service_1.CourtPosition.LIBERO_ZONE] != null ? [lineup[service_1.CourtPosition.LIBERO_ZONE].id] : []),
|
|
111
|
+
...lineup.bench.map((b) => b.id)
|
|
112
|
+
]);
|
|
102
113
|
const hasStartingLibero = lineup[service_1.CourtPosition.LIBERO_ZONE] != null;
|
|
103
|
-
const secondLibero = (hasStartingLibero && model.second_libero != null)
|
|
114
|
+
const secondLibero = (hasStartingLibero && model.second_libero != null && !inLineupIds.has(model.second_libero))
|
|
104
115
|
? roster.find((p) => p.id === model.second_libero)
|
|
105
116
|
: undefined;
|
|
106
117
|
return service_1.Tactics.create({
|
|
@@ -143,6 +154,23 @@ function transformToObject(model, roster) {
|
|
|
143
154
|
// to keep the active preset and the mirrored Tactics row in the same shape. Applies the same legacy fixups as
|
|
144
155
|
// the service transformer: resolve the designated-sub `mode` and drop a legacy 'NEVER' fatigueBand.
|
|
145
156
|
function tacticsColumnsToApiDto(model) {
|
|
157
|
+
// Same reserve-only fixup as the service transformer (transformToObject): a second libero with no starting
|
|
158
|
+
// libero, or one that already occupies the lineup (6 starters + libero zone + bench), is invalid, so don't
|
|
159
|
+
// surface it (or its swap config) to the API/preset shape -- otherwise the UI would reload and re-emit a
|
|
160
|
+
// collision that the server then rejects.
|
|
161
|
+
const lineupIds = new Set([
|
|
162
|
+
model.lineup[service_1.CourtPosition.LEFT_FRONT],
|
|
163
|
+
model.lineup[service_1.CourtPosition.MIDDLE_FRONT],
|
|
164
|
+
model.lineup[service_1.CourtPosition.RIGHT_FRONT],
|
|
165
|
+
model.lineup[service_1.CourtPosition.LEFT_BACK],
|
|
166
|
+
model.lineup[service_1.CourtPosition.MIDDLE_BACK],
|
|
167
|
+
model.lineup[service_1.CourtPosition.RIGHT_BACK],
|
|
168
|
+
...(model.lineup[service_1.CourtPosition.LIBERO_ZONE] != null ? [model.lineup[service_1.CourtPosition.LIBERO_ZONE]] : []),
|
|
169
|
+
...model.lineup.bench
|
|
170
|
+
]);
|
|
171
|
+
const validSecondLibero = (model.second_libero != null && model.lineup[service_1.CourtPosition.LIBERO_ZONE] != null && !lineupIds.has(model.second_libero))
|
|
172
|
+
? model.second_libero
|
|
173
|
+
: undefined;
|
|
146
174
|
return {
|
|
147
175
|
lineup: model.lineup,
|
|
148
176
|
substitutionTolerance: model.substitution_tolerance,
|
|
@@ -150,8 +178,8 @@ function tacticsColumnsToApiDto(model) {
|
|
|
150
178
|
pinchServerSubs: model.pinch_server_subs,
|
|
151
179
|
receiveRotationOffset: model.receive_rotation_offset,
|
|
152
180
|
substitutionBand: model.substitution_band,
|
|
153
|
-
secondLibero:
|
|
154
|
-
liberoSub: model.libero_sub,
|
|
181
|
+
secondLibero: validSecondLibero,
|
|
182
|
+
liberoSub: validSecondLibero != null ? model.libero_sub : undefined,
|
|
155
183
|
liberoReplacementSets: model.libero_replacement_sets,
|
|
156
184
|
designatedSubs: (model.designated_subs ?? []).map(d => ({
|
|
157
185
|
starterId: d.starterId,
|
|
@@ -63,4 +63,22 @@ function makeTactics(designatedSubs) {
|
|
|
63
63
|
(0, globals_1.expect)(dto.designatedSubs[0].fatigueBand).toBe(service_1.EnergyBand.WORN);
|
|
64
64
|
(0, globals_1.expect)(dto.designatedSubs[0].benchFatigueBand).toBe(service_1.EnergyBand.TIRED);
|
|
65
65
|
});
|
|
66
|
+
(0, globals_1.it)('drops a second libero that collides with the starting libero (not a reserve)', () => {
|
|
67
|
+
const model = makeTactics([]);
|
|
68
|
+
model.lineup[service_1.CourtPosition.LIBERO_ZONE] = 'libero1';
|
|
69
|
+
model.second_libero = 'libero1'; // same player as the starting libero -> invalid
|
|
70
|
+
model.libero_sub = { mode: 'FATIGUE' };
|
|
71
|
+
const dto = (0, tactics_1.tacticsColumnsToApiDto)(model);
|
|
72
|
+
(0, globals_1.expect)(dto.secondLibero).toBeUndefined();
|
|
73
|
+
(0, globals_1.expect)(dto.liberoSub).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
(0, globals_1.it)('keeps a second libero that is a genuine reserve', () => {
|
|
76
|
+
const model = makeTactics([]);
|
|
77
|
+
model.lineup[service_1.CourtPosition.LIBERO_ZONE] = 'libero1';
|
|
78
|
+
model.second_libero = 'reserve1'; // not in the lineup -> valid reserve
|
|
79
|
+
model.libero_sub = { mode: 'FATIGUE' };
|
|
80
|
+
const dto = (0, tactics_1.tacticsColumnsToApiDto)(model);
|
|
81
|
+
(0, globals_1.expect)(dto.secondLibero).toBe('reserve1');
|
|
82
|
+
(0, globals_1.expect)(dto.liberoSub).toEqual({ mode: 'FATIGUE' });
|
|
83
|
+
});
|
|
66
84
|
});
|
|
@@ -87,15 +87,26 @@ function buildPinchServerSubs(pinchServerSubs, roster) {
|
|
|
87
87
|
}
|
|
88
88
|
function transformToObject(model, roster) {
|
|
89
89
|
const lineup = transformToLineup(model.lineup, roster);
|
|
90
|
-
// A second libero only
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
90
|
+
// A second libero is only valid as a RESERVE: it needs a starting libero (LIBERO_ZONE), the L2 player must be
|
|
91
|
+
// on the roster, and it must NOT already occupy the lineup (the 6 starters, the libero zone, or the bench).
|
|
92
|
+
// Edge/legacy data can violate any of these -- the starting libero was removed without clearing the L2, the
|
|
93
|
+
// L2 retired/was dismissed, or the L2 is also the starting libero -- and any such row fails validation on
|
|
94
|
+
// load (SECOND_LIBERO_MUST_BE_RESERVE / REQUIRES_LIBERO) and would crash the whole sim batch. Drop the
|
|
95
|
+
// offending second libero (and, via the gate below, its swap config) so the team still loads and plays
|
|
96
|
+
// without an L2; the next tactics save rewrites it cleanly. roster.find (not findPlayer) so a gone L2 yields
|
|
96
97
|
// undefined instead of throwing.
|
|
98
|
+
const inLineupIds = new Set([
|
|
99
|
+
lineup[CourtPosition.LEFT_FRONT].id,
|
|
100
|
+
lineup[CourtPosition.MIDDLE_FRONT].id,
|
|
101
|
+
lineup[CourtPosition.RIGHT_FRONT].id,
|
|
102
|
+
lineup[CourtPosition.LEFT_BACK].id,
|
|
103
|
+
lineup[CourtPosition.MIDDLE_BACK].id,
|
|
104
|
+
lineup[CourtPosition.RIGHT_BACK].id,
|
|
105
|
+
...(lineup[CourtPosition.LIBERO_ZONE] != null ? [lineup[CourtPosition.LIBERO_ZONE].id] : []),
|
|
106
|
+
...lineup.bench.map((b) => b.id)
|
|
107
|
+
]);
|
|
97
108
|
const hasStartingLibero = lineup[CourtPosition.LIBERO_ZONE] != null;
|
|
98
|
-
const secondLibero = (hasStartingLibero && model.second_libero != null)
|
|
109
|
+
const secondLibero = (hasStartingLibero && model.second_libero != null && !inLineupIds.has(model.second_libero))
|
|
99
110
|
? roster.find((p) => p.id === model.second_libero)
|
|
100
111
|
: undefined;
|
|
101
112
|
return Tactics.create({
|
|
@@ -138,6 +149,23 @@ function transformToObject(model, roster) {
|
|
|
138
149
|
// to keep the active preset and the mirrored Tactics row in the same shape. Applies the same legacy fixups as
|
|
139
150
|
// the service transformer: resolve the designated-sub `mode` and drop a legacy 'NEVER' fatigueBand.
|
|
140
151
|
function tacticsColumnsToApiDto(model) {
|
|
152
|
+
// Same reserve-only fixup as the service transformer (transformToObject): a second libero with no starting
|
|
153
|
+
// libero, or one that already occupies the lineup (6 starters + libero zone + bench), is invalid, so don't
|
|
154
|
+
// surface it (or its swap config) to the API/preset shape -- otherwise the UI would reload and re-emit a
|
|
155
|
+
// collision that the server then rejects.
|
|
156
|
+
const lineupIds = new Set([
|
|
157
|
+
model.lineup[CourtPosition.LEFT_FRONT],
|
|
158
|
+
model.lineup[CourtPosition.MIDDLE_FRONT],
|
|
159
|
+
model.lineup[CourtPosition.RIGHT_FRONT],
|
|
160
|
+
model.lineup[CourtPosition.LEFT_BACK],
|
|
161
|
+
model.lineup[CourtPosition.MIDDLE_BACK],
|
|
162
|
+
model.lineup[CourtPosition.RIGHT_BACK],
|
|
163
|
+
...(model.lineup[CourtPosition.LIBERO_ZONE] != null ? [model.lineup[CourtPosition.LIBERO_ZONE]] : []),
|
|
164
|
+
...model.lineup.bench
|
|
165
|
+
]);
|
|
166
|
+
const validSecondLibero = (model.second_libero != null && model.lineup[CourtPosition.LIBERO_ZONE] != null && !lineupIds.has(model.second_libero))
|
|
167
|
+
? model.second_libero
|
|
168
|
+
: undefined;
|
|
141
169
|
return {
|
|
142
170
|
lineup: model.lineup,
|
|
143
171
|
substitutionTolerance: model.substitution_tolerance,
|
|
@@ -145,8 +173,8 @@ function tacticsColumnsToApiDto(model) {
|
|
|
145
173
|
pinchServerSubs: model.pinch_server_subs,
|
|
146
174
|
receiveRotationOffset: model.receive_rotation_offset,
|
|
147
175
|
substitutionBand: model.substitution_band,
|
|
148
|
-
secondLibero:
|
|
149
|
-
liberoSub: model.libero_sub,
|
|
176
|
+
secondLibero: validSecondLibero,
|
|
177
|
+
liberoSub: validSecondLibero != null ? model.libero_sub : undefined,
|
|
150
178
|
liberoReplacementSets: model.libero_replacement_sets,
|
|
151
179
|
designatedSubs: (model.designated_subs ?? []).map(d => ({
|
|
152
180
|
starterId: d.starterId,
|
|
@@ -61,4 +61,22 @@ describe('tacticsColumnsToApiDto()', () => {
|
|
|
61
61
|
expect(dto.designatedSubs[0].fatigueBand).toBe(EnergyBand.WORN);
|
|
62
62
|
expect(dto.designatedSubs[0].benchFatigueBand).toBe(EnergyBand.TIRED);
|
|
63
63
|
});
|
|
64
|
+
it('drops a second libero that collides with the starting libero (not a reserve)', () => {
|
|
65
|
+
const model = makeTactics([]);
|
|
66
|
+
model.lineup[CourtPosition.LIBERO_ZONE] = 'libero1';
|
|
67
|
+
model.second_libero = 'libero1'; // same player as the starting libero -> invalid
|
|
68
|
+
model.libero_sub = { mode: 'FATIGUE' };
|
|
69
|
+
const dto = tacticsColumnsToApiDto(model);
|
|
70
|
+
expect(dto.secondLibero).toBeUndefined();
|
|
71
|
+
expect(dto.liberoSub).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
it('keeps a second libero that is a genuine reserve', () => {
|
|
74
|
+
const model = makeTactics([]);
|
|
75
|
+
model.lineup[CourtPosition.LIBERO_ZONE] = 'libero1';
|
|
76
|
+
model.second_libero = 'reserve1'; // not in the lineup -> valid reserve
|
|
77
|
+
model.libero_sub = { mode: 'FATIGUE' };
|
|
78
|
+
const dto = tacticsColumnsToApiDto(model);
|
|
79
|
+
expect(dto.secondLibero).toBe('reserve1');
|
|
80
|
+
expect(dto.liberoSub).toEqual({ mode: 'FATIGUE' });
|
|
81
|
+
});
|
|
64
82
|
});
|