Show per-player TTR totals from completed singles only
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 57s

This commit is contained in:
Torsten Schulz (local)
2026-10-28 11:27:03 +01:00
parent 68b09a2fa0
commit 9d8c316e97
2 changed files with 92 additions and 47 deletions

View File

@@ -2,14 +2,14 @@ import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import vm from 'node:vm';
import { calculateTtrChange } from '../src/services/ttrCalculator.mjs';
import { calculateTtrEvent } from '../src/services/ttrCalculator.mjs';
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 requests = [];
const context = vm.createContext({ console, calculateTtrChange, apiClient: {
const context = vm.createContext({ console, calculateTtrEvent, apiClient: {
async post(url, body) {
requests.push({ url, body });
return { data: { players: [] } };
@@ -19,7 +19,7 @@ vm.runInContext(script, context);
const { methods, computed } = context.component;
function dialog() {
return Object.assign({}, methods, {
const report = Object.assign({}, methods, {
results: [
{ label: 'D1-D1', homeName: 'Anton Schottdorf / Anna Wolf', guestName: 'Yumin Kim / Timo Engler' },
{ label: '1. Einzel der Runde', homeName: 'Anton Schottdorf', guestName: 'Yumin Kim', completed: false },
@@ -27,6 +27,8 @@ function dialog() {
ttrSnapshots: {},
ttrPlayers: [],
});
Object.defineProperty(report, 'ttrSingles', { get: () => computed.ttrSingles.call(report) });
return report;
}
test('singles with a D in their title are retained while double formation labels are excluded', () => {
@@ -48,22 +50,59 @@ test('missing TTR values retain the single and are not treated as zero', () => {
assert.equal(rows.length, 1);
assert.equal(rows[0].homeTtr, 1300);
assert.equal(rows[0].guestTtr, null);
assert.equal(rows[0].preview, null);
assert.equal(rows[0].winner, null);
assert.equal(computed.ttrUnavailablePlayers.call(report)[0], 'Yumin Kim');
});
test('available ratings produce the preview and a completed single uses the entered result', () => {
test('ratings without results show no projection; a completed single shows new ratings for both players', () => {
const report = dialog();
report.applyTtrPreviewPlayers([
{ side: 'home', name: 'Anton Schottdorf', ttr: 1300 },
{ side: 'guest', name: 'Yumin Kim', ttr: 1300 },
]);
assert.equal(computed.ttrSingles.call(report)[0].preview.homeWin.displayChange, 8);
assert.ok(computed.ttrPlayerResults.call(report).every(player => player.projectedTtr === null));
report.results[1].completed = true;
report.getPlayerSetWins = (_match, side) => side === 'home' ? 3 : 0;
const row = computed.ttrSingles.call(report)[0];
assert.equal(row.actual.home.displayChange, 8);
assert.equal(row.actual.guest.displayChange, -8);
const players = computed.ttrPlayerResults.call(report);
assert.equal(players[0].projectedTtr, 1308);
assert.equal(players[1].projectedTtr, 1292);
assert.equal(players[0].completedCount, 1);
});
test('player totals use the original snapshot and round once across completed singles only', () => {
const report = dialog();
report.results[0].completed = true; // A double must never enter the total.
report.results[1].completed = true;
report.results.push({ label: 'E1-E2', homeName: 'Anton Schottdorf', guestName: 'Timo Engler', completed: true });
report.results.push({ label: 'E1-E3', homeName: 'Anton Schottdorf', guestName: 'Ramón Hirschfeld', completed: false });
report.getPlayerSetWins = (_match, side) => side === 'home' ? 3 : 0;
report.applyTtrPreviewPlayers([
{ side: 'home', name: 'Anton Schottdorf', ttr: 1318 },
{ side: 'guest', name: 'Yumin Kim', ttr: 1382 },
{ side: 'guest', name: 'Timo Engler', ttr: 1429 },
{ side: 'guest', name: 'Ramón Hirschfeld', ttr: 1500 },
]);
let players = computed.ttrPlayerResults.call(report);
assert.equal(players[0].completedCount, 2);
assert.equal(players[0].projectedTtr, 1343);
assert.equal(players.find(player => player.name === 'Ramón Hirschfeld').projectedTtr, null);
report.results[1].completed = false;
report.results[2].completed = false;
players = computed.ttrPlayerResults.call(report);
assert.equal(players[0].completedCount, 0);
assert.equal(players[0].projectedTtr, null);
});
test('missing opponent ratings prevent misleading partial totals for completed results', () => {
const report = dialog();
report.results[1].completed = true;
report.getPlayerSetWins = (_match, side) => side === 'home' ? 3 : 0;
report.applyTtrPreviewPlayers([{ side: 'home', name: 'Anton Schottdorf', ttr: 1300 }]);
const players = computed.ttrPlayerResults.call(report);
assert.equal(players[0].completedCount, 1);
assert.equal(players[0].missingOpponentTtr, true);
assert.equal(players[0].projectedTtr, null);
assert.equal(players[1].projectedTtr, null);
});
test('standalone test mode calls the API and keeps real pairings without inventing ratings', async () => {
@@ -82,7 +121,7 @@ test('standalone test mode calls the API and keeps real pairings without inventi
const rows = computed.ttrSingles.call(report);
assert.equal(rows.length, 1);
assert.equal(rows[0].homeTtr, null);
assert.equal(rows[0].preview, null);
assert.equal(rows[0].winner, null);
});
test('TTR requests include displayed singles even when legacy lineup metadata is missing', () => {