Fix nuScore double lineup synchronization
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 1m0s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 1m0s
This commit is contained in:
@@ -860,6 +860,7 @@ import CryptoJS from 'crypto-js';
|
||||
import apiClient, { backendBaseUrl } from '../apiClient';
|
||||
import { onMatchReportSubmitted, offMatchReportSubmitted } from '../services/socketService.js';
|
||||
import { calculateTtrChange } from '../services/ttrCalculator.mjs';
|
||||
import { resolveLineupToken } from '../services/lineupResolver.mjs';
|
||||
|
||||
export default {
|
||||
name: 'MatchReportDialog',
|
||||
@@ -1500,7 +1501,7 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
|
||||
// Effektive Spieleranzahl (berücksichtigt Braunschweiger-Regel)
|
||||
getEffectivePlayerCount(team) {
|
||||
const actualCount = this.getSelectedPlayerCount(team);
|
||||
const actualCount = this.getSinglePlayerCount(team);
|
||||
const playMode = this.getPlayMode();
|
||||
|
||||
// Spezieller Fall für Braunschweiger System: 2er-Mannschaften werden als 3er-Mannschaften gewertet
|
||||
@@ -2396,13 +2397,20 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
|
||||
if (result?.object) {
|
||||
const hydratedResult = JSON.parse(JSON.stringify(result.object));
|
||||
this.applyCurrentLineupStateToPayload(hydratedResult);
|
||||
this.meetingDetails = hydratedResult;
|
||||
// /validate may return its default (for example 4:3)
|
||||
// match order even though all four players and both
|
||||
// doubles are locally assigned. Restore the local lineup
|
||||
// and derive the formations again before rendering.
|
||||
this.applyCurrentLineupStateToPayload(this.meetingDetails);
|
||||
this.updatePlayerPositions(this.meetingDetails);
|
||||
this.applyBraunschweigerMatchFormations(this.meetingDetails);
|
||||
this.meetingData = {
|
||||
...(this.meetingData || {}),
|
||||
...JSON.parse(JSON.stringify(result.object))
|
||||
};
|
||||
this.applyLineupCertificationFromMeetingDetails();
|
||||
this.prepareResults();
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
@@ -2905,6 +2913,14 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
},
|
||||
|
||||
resolveMatchSide(match, index, side) {
|
||||
const label = this.getMatchFormationLabel(match, index);
|
||||
const token = String(label).split('–')[side === 'home' ? 0 : 1]?.trim();
|
||||
const key = side === 'home' ? 'teamLineupHomePlayers' : 'teamLineupGuestPlayers';
|
||||
const lineupName = resolveLineupToken(this.meetingDetails?.[key], token);
|
||||
// Current positions override cached nuScore match participants,
|
||||
// including explicitly cleared positions.
|
||||
if (lineupName !== null) return lineupName;
|
||||
|
||||
const assignedName = this.formatMeetingMatchSide(match, side);
|
||||
if (this.isUsableMatchSide(assignedName)) {
|
||||
return assignedName;
|
||||
@@ -3263,6 +3279,9 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
}
|
||||
},
|
||||
prepareResults() {
|
||||
if (this.getSinglePlayerCount('home') > 0 && this.getSinglePlayerCount('guest') > 0) {
|
||||
this.applyBraunschweigerMatchFormations(this.meetingDetails);
|
||||
}
|
||||
// nuScore kann Aufstellungen liefern, die sich nicht aus der Zahl
|
||||
// ausgewählter Spieler ableiten lassen (z.B. ein Spieler nur im
|
||||
// Doppel). Die vom Server gelieferten Begegnungen sind dann für
|
||||
@@ -3367,6 +3386,10 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.getSinglePlayerCount('home') > 0 && this.getSinglePlayerCount('guest') > 0 &&
|
||||
this.applyBraunschweigerMatchFormations(this.meetingDetails)) {
|
||||
this.prepareResults();
|
||||
}
|
||||
const sourceMatches = this.meetingDetails.matches;
|
||||
const count = Math.min((this.results && this.results.length) || 0, sourceMatches.length);
|
||||
|
||||
@@ -3380,12 +3403,8 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
if (typeof src.name === 'string' && src.name.trim() !== '') {
|
||||
dst.label = src.name.trim();
|
||||
}
|
||||
if (resolvedHomeName) {
|
||||
dst.homeName = resolvedHomeName;
|
||||
}
|
||||
if (resolvedGuestName) {
|
||||
dst.guestName = resolvedGuestName;
|
||||
}
|
||||
dst.homeName = resolvedHomeName;
|
||||
dst.guestName = resolvedGuestName;
|
||||
|
||||
if (!dst.sets || !Array.isArray(dst.sets)) {
|
||||
this.$set(dst, 'sets', ['', '', '', '', '']);
|
||||
@@ -3924,123 +3943,17 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
const token = side === 'home' ? parts[0] : parts[1];
|
||||
if (!token) return '';
|
||||
|
||||
if (token.startsWith('DA') || token.startsWith('DB')) {
|
||||
// Doppel-Name aus den gesetzten Doppeln ableiten
|
||||
const pos = parseInt(token.replace(/[^0-9]/g, '')); // 1 oder 2
|
||||
const key = side === 'home' ? 'teamLineupHomePlayers' : 'teamLineupGuestPlayers';
|
||||
const list = (this.meetingDetails && this.meetingDetails[key]) ? this.meetingDetails[key] : [];
|
||||
const pair = list.filter(p => p.positionDouble === pos && p.nuLigaPersonId > 0);
|
||||
const n = (p) => [p.firstname || p.firstName || '', p.lastname || p.lastName || ''].filter(Boolean).join(' ').trim();
|
||||
|
||||
// Wenn ein Doppel-Paar gefunden wurde, verwende es
|
||||
if (pair.length >= 2) {
|
||||
const result = `${n(pair[0])} / ${n(pair[1])}`;
|
||||
return result;
|
||||
}
|
||||
if (pair.length === 1) {
|
||||
const result = `${n(pair[0])}`;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Falls kein Doppel gefunden, verwende die entsprechenden Spieler basierend auf der Position
|
||||
if (pair.length === 0) {
|
||||
const selectedPlayers = list.filter(p => p.isSelected && p.nuLigaPersonId > 0);
|
||||
|
||||
|
||||
// Versuche verschiedene Sortierungen
|
||||
// 1. Zuerst nach positionDouble (falls vorhanden)
|
||||
// 2. Dann nach rank
|
||||
// 3. Dann nach der Reihenfolge in der Liste
|
||||
const sortedPlayers = selectedPlayers.sort((a, b) => {
|
||||
// Spieler mit positionDouble zuerst
|
||||
if (a.positionDouble && !b.positionDouble) return -1;
|
||||
if (!a.positionDouble && b.positionDouble) return 1;
|
||||
if (a.positionDouble && b.positionDouble) {
|
||||
return a.positionDouble - b.positionDouble;
|
||||
}
|
||||
|
||||
// Dann nach rank
|
||||
if (a.rank !== b.rank) {
|
||||
return a.rank - b.rank;
|
||||
}
|
||||
|
||||
// Dann nach positions
|
||||
const aPos = (a.positions && a.positions.length > 0) ? a.positions[0] : 'Z';
|
||||
const bPos = (b.positions && b.positions.length > 0) ? b.positions[0] : 'Z';
|
||||
return aPos.localeCompare(bPos);
|
||||
});
|
||||
|
||||
|
||||
// Für DA1/DB1: Spieler 0+1, für DA2/DB2: Spieler 2+3, etc.
|
||||
const startIndex = (pos - 1) * 2;
|
||||
if (sortedPlayers.length > startIndex + 1) {
|
||||
const result = `${n(sortedPlayers[startIndex])} / ${n(sortedPlayers[startIndex + 1])}`;
|
||||
return result;
|
||||
} else if (sortedPlayers.length > startIndex) {
|
||||
const result = n(sortedPlayers[startIndex]);
|
||||
return result;
|
||||
} else if (sortedPlayers.length >= 2) {
|
||||
// Fallback: zirkulieren
|
||||
const index1 = startIndex % sortedPlayers.length;
|
||||
const index2 = (startIndex + 1) % sortedPlayers.length;
|
||||
const result = `${n(sortedPlayers[index1])} / ${n(sortedPlayers[index2])}`;
|
||||
return result;
|
||||
} else if (sortedPlayers.length === 1) {
|
||||
const result = n(sortedPlayers[0]);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return token;
|
||||
} else {
|
||||
// Einzelposition A1..A6 / B1..B6
|
||||
const isHome = token.startsWith('A');
|
||||
const rank = parseInt(token.replace(/[^0-9]/g, ''));
|
||||
const team = (isHome ? 'home' : 'guest');
|
||||
const key = team === 'home' ? 'teamLineupHomePlayers' : 'teamLineupGuestPlayers';
|
||||
const list = (this.meetingDetails && this.meetingDetails[key]) ? this.meetingDetails[key] : [];
|
||||
|
||||
// Prüfe zuerst, ob überhaupt genügend Spieler für diese Position vorhanden sind
|
||||
const selectedPlayers = list.filter(p => p.isSelected && p.nuLigaPersonId > 0);
|
||||
|
||||
// Für Braunschweiger System: 2er-Mannschaften werden als 3er-Mannschaften gewertet
|
||||
let effectivePlayerCount = selectedPlayers.length;
|
||||
const playMode = this.getPlayMode();
|
||||
if (playMode && playMode.toLowerCase().includes('braunschweiger') && selectedPlayers.length === 2) {
|
||||
effectivePlayerCount = 3;
|
||||
}
|
||||
|
||||
if (effectivePlayerCount < rank) {
|
||||
// Nicht genügend Spieler für diese Position → Position bleibt leer
|
||||
return '';
|
||||
}
|
||||
|
||||
// Suche nach Spieler mit entsprechender Position
|
||||
// Zuerst nach A1, A2, etc. in positions Array (für Standard-Aufstellungen)
|
||||
let player = list.find(p => p.isSelected && (p.positions || []).includes(`${isHome ? 'A' : 'B'}${rank}`));
|
||||
|
||||
// Falls immer noch nicht gefunden, suche nach Spielern mit E1, E2, etc. in positions
|
||||
if (!player) {
|
||||
player = list.find(p => p.isSelected && (p.positions || []).includes(`E${rank}`));
|
||||
}
|
||||
|
||||
// Falls immer noch nicht gefunden, nach Reihenfolge in der Liste der ausgewählten Spieler
|
||||
if (!player) {
|
||||
// Da wir bereits oben geprüft haben, dass genügend Spieler vorhanden sind,
|
||||
// können wir sicher den entsprechenden Spieler verwenden
|
||||
const sortedPlayers = selectedPlayers.sort((a, b) => {
|
||||
const aPos = (a.positions && a.positions.length > 0) ? a.positions[0] : 'Z';
|
||||
const bPos = (b.positions && b.positions.length > 0) ? b.positions[0] : 'Z';
|
||||
return aPos.localeCompare(bPos);
|
||||
});
|
||||
|
||||
player = sortedPlayers[rank - 1];
|
||||
}
|
||||
|
||||
if (!player) return '';
|
||||
const result = [player.firstname || player.firstName || '', player.lastname || player.lastName || ''].filter(Boolean).join(' ').trim();
|
||||
return result;
|
||||
}
|
||||
const key = side === 'home' ? 'teamLineupHomePlayers' : 'teamLineupGuestPlayers';
|
||||
const list = this.meetingDetails?.[key] || [];
|
||||
const resolved = resolveLineupToken(list, token);
|
||||
if (resolved !== null) return resolved;
|
||||
|
||||
// Legacy drafts without positions may use selection order for
|
||||
// singles only. Doubles require an explicit pair.
|
||||
if (/^D/i.test(token)) return '';
|
||||
const position = Number(token.replace(/\D/g, ''));
|
||||
const selected = list.filter(player => player.isSelected && Number(player.nuLigaPersonId) > 0);
|
||||
return this.formatMatchPlayerName(selected[position - 1]);
|
||||
} catch (e) {
|
||||
console.warn('Fehler beim Auflösen der Spielerseite:', e, 'label:', label, 'side:', side);
|
||||
return '';
|
||||
@@ -4830,6 +4743,11 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
selectedPlayer.positionDouble = doublePosition;
|
||||
}
|
||||
|
||||
// nuScore evaluates both positionDouble and positions. Keep
|
||||
// both representations in sync immediately, rather than only
|
||||
// when the final report is submitted.
|
||||
this.updatePlayerPositions(this.meetingDetails);
|
||||
|
||||
// Sowohl Setzen als auch Entfernen muss die Doppelzeilen
|
||||
// unmittelbar aus den aktuellen Paaren neu auflösen.
|
||||
if (!this.isHomeLineupCertified || !this.isGuestLineupCertified) {
|
||||
@@ -4853,11 +4771,6 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if player is already selected for a different double position
|
||||
if (player.positionDouble && player.positionDouble !== doublePosition) {
|
||||
return false; // Player is already assigned to a different double position
|
||||
}
|
||||
|
||||
// Pro Doppelposition sind genau zwei aufgestellte Spieler erlaubt.
|
||||
const playersInDoublePosition = this.meetingDetails[teamKey].filter(p =>
|
||||
p.isSelected && p.positionDouble === doublePosition && p.nuLigaPersonId !== player.nuLigaPersonId && p.nuLigaPersonId > 0
|
||||
@@ -4890,6 +4803,8 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
this.meetingDetails[teamKey][playerIndex].positions = [`E${index + 1}`];
|
||||
}
|
||||
});
|
||||
|
||||
this.updatePlayerPositions(this.meetingDetails);
|
||||
},
|
||||
|
||||
// PINs automatisch laden
|
||||
@@ -5354,10 +5269,36 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
return;
|
||||
}
|
||||
|
||||
const previousFormations = target.meetingPlayMode?.matchFormations || [];
|
||||
const formationKey = formation => `${formation?.homeLineupPosition}-${formation?.guestLineupPosition}`;
|
||||
const changed = previousFormations.length !== formations.length ||
|
||||
formations.some((formation, index) => formationKey(formation) !== formationKey(previousFormations[index]));
|
||||
if (changed && Array.isArray(target.matches) && target.matches.length > 0) {
|
||||
// Keep scores with their formation when the team size changes;
|
||||
// a new D2 must never inherit the former second singles match.
|
||||
const previousByFormation = new Map(previousFormations.map((formation, index) => [
|
||||
formationKey(formation),
|
||||
target.matches.find(match => Number(match.matchNr) === Number(formation.matchNr)) || target.matches[index]
|
||||
]));
|
||||
target.matches = formations.map(formation => {
|
||||
const previous = previousByFormation.get(formationKey(formation));
|
||||
return {
|
||||
...(previous || {
|
||||
set1A: 0, set1B: 0, set2A: 0, set2B: 0, set3A: 0, set3B: 0,
|
||||
set4A: 0, set4B: 0, set5A: 0, set5B: 0,
|
||||
matchesA: 0, matchesB: 0, setsA: 0, setsB: 0, isCompleted: false
|
||||
}),
|
||||
matchNr: formation.matchNr,
|
||||
name: `${formation.homeLineupPosition}-${formation.guestLineupPosition}`,
|
||||
matchType: formation.homeLineupPosition.startsWith('D') ? 'DOUBLE' : 'SINGLE'
|
||||
};
|
||||
});
|
||||
}
|
||||
target.meetingPlayMode = {
|
||||
...(target.meetingPlayMode && typeof target.meetingPlayMode === 'object' ? target.meetingPlayMode : {}),
|
||||
matchFormations: formations
|
||||
};
|
||||
return changed;
|
||||
},
|
||||
getBraunschweigerServerMatrix() {
|
||||
const mapFormationsToLabels = (formations) => {
|
||||
|
||||
27
frontend/src/services/lineupResolver.mjs
Normal file
27
frontend/src/services/lineupResolver.mjs
Normal file
@@ -0,0 +1,27 @@
|
||||
const nameOf = player => [player.firstname || player.firstName, player.lastname || player.lastName]
|
||||
.filter(Boolean).join(' ').trim();
|
||||
|
||||
// null means the response has no position data; an empty string means that
|
||||
// position is explicitly unoccupied. Never fill doubles from ranking order.
|
||||
export function resolveLineupToken(players, token) {
|
||||
const match = /^(D[AB]|[AB])(\d+)$/i.exec(String(token || '').trim());
|
||||
if (!match) return null;
|
||||
const double = match[1].length === 2;
|
||||
const position = Number(match[2]);
|
||||
const field = double ? 'positionDouble' : 'positionSingle';
|
||||
const positionPattern = double ? /^D\d+$/i : /^(E|A|B)\d+$/i;
|
||||
const list = (players || []).filter(player => Number(player?.nuLigaPersonId) > 0);
|
||||
const hasPositionData = list.some(player =>
|
||||
Object.hasOwn(player, field) || (player.positions || []).some(value => positionPattern.test(value)));
|
||||
if (!hasPositionData) return null;
|
||||
|
||||
const assigned = list.filter(player => {
|
||||
if (!double && !player.isSelected) return false;
|
||||
// An explicit null also overrides obsolete entries in positions.
|
||||
if (Object.hasOwn(player, field)) return Number(player[field]) === position;
|
||||
return (player.positions || []).some(value =>
|
||||
positionPattern.test(value) && Number(String(value).replace(/\D/g, '')) === position);
|
||||
});
|
||||
if (double) return assigned.map(nameOf).filter(Boolean).join(' / ');
|
||||
return assigned.length === 1 ? nameOf(assigned[0]) : '';
|
||||
}
|
||||
146
frontend/tests/lineupResolver.test.mjs
Normal file
146
frontend/tests/lineupResolver.test.mjs
Normal file
@@ -0,0 +1,146 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import vm from 'node:vm';
|
||||
import { resolveLineupToken } from '../src/services/lineupResolver.mjs';
|
||||
|
||||
// Exercise the actual dialog methods without mounting its network/UI lifecycle.
|
||||
const source = readFileSync(new URL('../src/components/MatchReportApiDialog.vue', import.meta.url), 'utf8');
|
||||
const script = source.match(/<script>([\s\S]*?)<\/script>/)[1]
|
||||
.replace(/^import .*;\n/gm, '')
|
||||
.replace('export default', 'globalThis.component =');
|
||||
const context = vm.createContext({ resolveLineupToken, console });
|
||||
vm.runInContext(script, context);
|
||||
const methods = context.component.methods;
|
||||
const player = (id, name, single, double, selected = true) => ({
|
||||
nuLigaPersonId: id, firstname: name, isSelected: selected,
|
||||
positionSingle: single, positionDouble: double,
|
||||
});
|
||||
const home = [
|
||||
player(1, 'Fred Swyter', null, null, false),
|
||||
player(2, 'Lukas Wypior', 1, 1),
|
||||
player(3, 'Magdalena Schwalbach', 2, 1),
|
||||
player(4, 'Timo Wolf', 3, 2),
|
||||
player(5, 'Joshua Koch', 4, 2),
|
||||
];
|
||||
const guest = [
|
||||
player(11, 'Anton Schottdorf', 1, 1),
|
||||
player(12, 'Anna Wolf', 2, 2),
|
||||
player(13, 'Raffaele Garofano', 3, 2),
|
||||
player(14, 'Edis Toprak Basaraner', 4, 1),
|
||||
];
|
||||
function dialog(players = structuredClone(home)) {
|
||||
return Object.assign({}, methods, {
|
||||
meetingDetails: {
|
||||
teamLineupHomePlayers: players,
|
||||
teamLineupGuestPlayers: structuredClone(guest),
|
||||
meetingPlayMode: { matchFormations: [
|
||||
{ matchNr: 1, homeLineupPosition: 'D1', guestLineupPosition: 'D1' },
|
||||
{ matchNr: 2, homeLineupPosition: 'D2', guestLineupPosition: 'D2' },
|
||||
{ matchNr: 3, homeLineupPosition: 'E1', guestLineupPosition: 'E2' },
|
||||
] },
|
||||
},
|
||||
currentPlayModeMatrix: [],
|
||||
});
|
||||
}
|
||||
|
||||
test('screenshot lineup resolves both explicit doubles, including the crossed guest pairs', () => {
|
||||
const report = dialog();
|
||||
assert.equal(report.resolveMatchSide({}, 0, 'home'), 'Lukas Wypior / Magdalena Schwalbach');
|
||||
assert.equal(report.resolveMatchSide({}, 0, 'guest'), 'Anton Schottdorf / Edis Toprak Basaraner');
|
||||
assert.equal(report.resolveMatchSide({}, 1, 'home'), 'Timo Wolf / Joshua Koch');
|
||||
assert.equal(report.resolveMatchSide({}, 1, 'guest'), 'Anna Wolf / Raffaele Garofano');
|
||||
});
|
||||
|
||||
test('current positions override obsolete match names and unselected singles', () => {
|
||||
const report = dialog();
|
||||
const stale = { playerA1: { firstname: 'Fred Swyter' }, homePlayer: 'Fred Swyter' };
|
||||
assert.equal(report.resolveMatchSide(stale, 2, 'home'), 'Lukas Wypior');
|
||||
assert.equal(report.resolveMatchSide(stale, 0, 'home'), 'Lukas Wypior / Magdalena Schwalbach');
|
||||
assert.equal(report.resolveSide('A4 – B4', 'home'), 'Joshua Koch');
|
||||
});
|
||||
|
||||
test('clearing a double does not resurrect server names or invent a pair from rank', () => {
|
||||
const report = dialog();
|
||||
for (const p of report.meetingDetails.teamLineupHomePlayers) p.positionDouble = null;
|
||||
assert.equal(report.resolveMatchSide({ homePlayer: 'Old / Pair' }, 0, 'home'), '');
|
||||
assert.equal(report.resolveSide('DA1 – DB1', 'home'), '');
|
||||
assert.equal(dialog([{ nuLigaPersonId: 1, firstname: 'One', isSelected: true },
|
||||
{ nuLigaPersonId: 2, firstname: 'Two', isSelected: true }]).resolveSide('DA1 – DB1', 'home'), '');
|
||||
});
|
||||
|
||||
test('nuScore participants remain available when lineup positions are absent', () => {
|
||||
const report = dialog([]);
|
||||
assert.equal(report.resolveMatchSide({ playerA1: { firstname: 'One' }, playerA2: { firstname: 'Two' } }, 0, 'home'), 'One / Two');
|
||||
assert.equal(report.resolveMatchSide({ homePlayer: 'One / Two' }, 0, 'home'), 'One / Two');
|
||||
assert.equal(report.resolveMatchSide({ homePlayer: 'nicht angegeben' }, 0, 'home'), '');
|
||||
});
|
||||
|
||||
test('numeric strings and legacy positions work; double-only players stay out of singles', () => {
|
||||
const players = [player(1, 'One', '1', '2'), player(2, 'Two', null, '2', false)];
|
||||
assert.equal(resolveLineupToken(players, 'DA2'), 'One / Two');
|
||||
assert.equal(resolveLineupToken(players, 'A2'), '');
|
||||
assert.equal(resolveLineupToken([{ nuLigaPersonId: 1, firstname: 'Legacy', isSelected: true, positions: ['E1', 'D2'] }], 'DB2'), 'Legacy');
|
||||
assert.equal(resolveLineupToken([{ ...players[0], positionDouble: null, positions: ['D2'] }], 'DA2'), '');
|
||||
});
|
||||
|
||||
test('formation lookup uses match number rather than array order', () => {
|
||||
assert.equal(dialog().resolveMatchSide({ matchNr: 2 }, 0, 'guest'), 'Anna Wolf / Raffaele Garofano');
|
||||
});
|
||||
|
||||
test('a stale three-player matrix gains D2 for four against four without moving singles scores into it', () => {
|
||||
const report = dialog();
|
||||
report.getPlayMode = () => 'braunschweiger system';
|
||||
report.currentPlayModeMatrix = [
|
||||
'DA1 – DB1', 'DA2 – DB2', 'A1 – B1', 'A2 – B2', 'A3 – B3',
|
||||
'A4 – B4', 'A1 – B2', 'A2 – B1', 'A3 – B4', 'A4 – B3',
|
||||
];
|
||||
report.meetingDetails.meetingPlayMode.matchFormations = [
|
||||
{ matchNr: 1, homeLineupPosition: 'D1', guestLineupPosition: 'D1' },
|
||||
{ matchNr: 2, homeLineupPosition: 'E1', guestLineupPosition: 'E2' },
|
||||
];
|
||||
report.meetingDetails.matches = [
|
||||
{ matchNr: 1, set1A: 11, set1B: 5 },
|
||||
{ matchNr: 2, set1A: 11, set1B: 8, playerA1: { firstname: 'Fred Swyter' } },
|
||||
];
|
||||
report.ensureTtrSnapshots = () => {};
|
||||
report.prepareResults();
|
||||
assert.equal(report.results.length, 10);
|
||||
assert.equal(report.results[1].homeName, 'Timo Wolf / Joshua Koch');
|
||||
assert.equal(report.results[1].guestName, 'Anna Wolf / Raffaele Garofano');
|
||||
assert.equal(report.meetingDetails.matches[1].set1A, 0);
|
||||
assert.equal(report.meetingDetails.matches[6].set1B, 8);
|
||||
assert.equal(report.meetingDetails.matches[6].matchNr, 7);
|
||||
assert.equal(report.meetingDetails.matches[0].set1B, 5);
|
||||
});
|
||||
|
||||
test('an extra double-only player does not change the Braunschweig team-size matrix', () => {
|
||||
const report = dialog([...structuredClone(home), player(100, 'Reserve', null, 2)]);
|
||||
report.getPlayMode = () => 'braunschweiger system';
|
||||
assert.equal(report.getEffectivePlayerCount('home'), 4);
|
||||
});
|
||||
|
||||
test('a player may move between doubles without having to be the final selection', () => {
|
||||
const report = dialog();
|
||||
const lukas = report.meetingDetails.teamLineupHomePlayers[1];
|
||||
const timo = report.meetingDetails.teamLineupHomePlayers[3];
|
||||
// D2 contains Timo and Joshua, so it is full; after freeing one place,
|
||||
// Lukas can be moved directly from D1 to D2.
|
||||
timo.positionDouble = null;
|
||||
report.persistLocalDraftDebounced = () => {};
|
||||
report.broadcastDraftDebounced = () => {};
|
||||
report.syncDraftToServerDebounced = () => {};
|
||||
assert.equal(report.canSelectDoublePosition(lukas, 'home', 2), true);
|
||||
report.toggleDoublePosition(lukas, 'home', 2);
|
||||
assert.equal(lukas.positionDouble, 2);
|
||||
assert.deepEqual(Array.from(lukas.positions), ['E1', 'D2']);
|
||||
});
|
||||
|
||||
test('position arrays retain both doubles after any single-position recalculation', () => {
|
||||
const report = dialog();
|
||||
report.recalculatePositions('teamLineupHomePlayers');
|
||||
assert.deepEqual(Array.from(report.meetingDetails.teamLineupHomePlayers[1].positions), ['E1', 'D1']);
|
||||
assert.deepEqual(Array.from(report.meetingDetails.teamLineupHomePlayers[2].positions), ['E2', 'D1']);
|
||||
assert.deepEqual(Array.from(report.meetingDetails.teamLineupHomePlayers[3].positions), ['E3', 'D2']);
|
||||
assert.deepEqual(Array.from(report.meetingDetails.teamLineupHomePlayers[4].positions), ['E4', 'D2']);
|
||||
});
|
||||
Reference in New Issue
Block a user