Rueckfragen im Stil der Anwendung, Kulanz-Ausgleich, Redesign aus dem CC-Topf
window.confirm und window.prompt sind raus - alle vierzehn Stellen. Sie sahen nicht nur fremd aus: Wer sie im Browser abschaltet, bekommt stillschweigend "nein" zurueck, und eine Aktion passiert einfach nicht, ohne dass jemand erfaehrt warum. Stattdessen gibt es einen Dialog-Dienst mit drei Formen - bestaetigen, abfragen, waehlen -, der sich wie der Rest der Anwendung anfuehlt. Die Wahl kam dazu, weil eine Stelle ein Ja/Nein fuer eine Auswahl missbrauchte: "OK = Kulanz, Abbrechen = bezahltes Redesign". Das fuehrt jeden in die Irre, der es zum ersten Mal sieht. Neu: Steht der bezahlte Redesign-Topf im Minus, waehrend Kulanz uebrig ist, erscheint "Minus mit Kulanz ausgleichen". Es bucht so viele Uebergaenge um, wie beide Seiten hergeben - die aeltesten zuerst, damit das Ergebnis wiederholbar ist. Unterm Strich aendert sich nichts, nur die Verteilung. Ebenfalls neu: Ein Redesign laesst sich ausnahmsweise aus dem CC-Topf bezahlen. Damit das Ausrichten an den Ketten so eine Entscheidung nicht beim naechsten Umsortieren wieder einebnet, tragen von Hand gewaehlte Buchungen jetzt einen Merker. Dabei aufgefallen: Die Tests wurden gar nicht typgeprueft - tsconfig schloss nur src ein. Sieben Aufrufe von bookKulanz hatten vertauschte Argumente und liefen trotzdem durch, weil JavaScript das hinnimmt; sie pruefte damit etwas anderes als gedacht. Tests haengen jetzt in der Typpruefung.
This commit is contained in:
+137
-4
@@ -447,7 +447,7 @@ describe('Redesign nachträglich umbuchen', () => {
|
||||
);
|
||||
assert.equal(zwei.balances.REDESIGN_PAID, 0, 'das bezahlte ist verbraucht');
|
||||
|
||||
await credits.bookKulanz(spieler, 1, 'Absprache aufgetaucht', ctx);
|
||||
await credits.bookKulanz(spieler, ctx, { note: 'Absprache aufgetaucht' });
|
||||
const danach = await characters.changeFunding(zwei.character.id, 'REDESIGN_KULANZ', ctx);
|
||||
|
||||
assert.equal(danach.balances.REDESIGN_PAID, 1, 'das bezahlte ist wieder frei');
|
||||
@@ -466,7 +466,7 @@ describe('Redesign nachträglich umbuchen', () => {
|
||||
it('und wieder zurück', async () => {
|
||||
const spieler = await spielerMitPaketen('Hin und her', 1);
|
||||
const eins = await characters.createCharacter(spieler, { name: 'A' }, ctx);
|
||||
await credits.bookKulanz(spieler, 1, 'Erstmal Kulanz', ctx);
|
||||
await credits.bookKulanz(spieler, ctx, { note: 'Erstmal Kulanz' });
|
||||
const zwei = await characters.redesignCharacter(
|
||||
eins.character.id,
|
||||
{ name: 'B', useKind: 'REDESIGN_KULANZ' },
|
||||
@@ -681,7 +681,7 @@ describe('Die Stelle in der Kette bestimmt, was ein Eintrag kostet', () => {
|
||||
it('lässt eine Kulanz-Entscheidung unangetastet', async () => {
|
||||
const spieler = await spielerMitPaketen('Kulanzfall', 2);
|
||||
const eins = await characters.createCharacter(spieler, { name: 'Basis' }, ctx);
|
||||
await credits.bookKulanz(spieler, 1, 'Absprache', ctx);
|
||||
await credits.bookKulanz(spieler, ctx, { note: 'Absprache' });
|
||||
const zwei = await characters.redesignCharacter(
|
||||
eins.character.id,
|
||||
{ name: 'Aus Kulanz', useKind: 'REDESIGN_KULANZ' },
|
||||
@@ -951,7 +951,7 @@ describe('Die Rechnung geht auf', () => {
|
||||
it('lässt eine Kulanz-Entscheidung auch beim Nachrechnen stehen', async () => {
|
||||
const spieler = await spielerMitPaketen('Kulanz bleibt', 1);
|
||||
const eins = await characters.createCharacter(spieler, { name: 'Start' }, ctx);
|
||||
await credits.bookKulanz(spieler, 1, 'Absprache', ctx);
|
||||
await credits.bookKulanz(spieler, ctx, { note: 'Absprache' });
|
||||
const zwei = await characters.redesignCharacter(
|
||||
eins.character.id,
|
||||
{ name: 'Aus Kulanz', useKind: 'REDESIGN_KULANZ' },
|
||||
@@ -967,3 +967,136 @@ describe('Die Rechnung geht auf', () => {
|
||||
assert.equal(buchung?.kind, 'REDESIGN_KULANZ');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Kulanz gegen ein Minus verrechnen', () => {
|
||||
/** Ein Spieler mit Redesigns im Minus und Kulanz auf der Seite. */
|
||||
async function schieflage() {
|
||||
const spieler = await spielerMitPaketen('Schieflage', 1);
|
||||
const erster = await characters.createCharacter(spieler, { name: 'Anfang' }, ctx);
|
||||
|
||||
// Vier Redesigns, aber nur eines bezahlt - der Topf geht ins Minus.
|
||||
let letzter = erster.character.id;
|
||||
for (let i = 2; i <= 5; i += 1) {
|
||||
const naechster = await characters.redesignCharacter(
|
||||
letzter,
|
||||
{ name: `Stufe ${i}`, useKind: 'REDESIGN_PAID' },
|
||||
ctx,
|
||||
);
|
||||
letzter = naechster.character.id;
|
||||
}
|
||||
|
||||
const stand = await credits.getBalances(spieler);
|
||||
assert.equal(stand.REDESIGN_PAID, -3, 'vier verbraucht, eines aus dem Paket');
|
||||
return spieler;
|
||||
}
|
||||
|
||||
it('gleicht so viel aus, wie beide Seiten hergeben', async () => {
|
||||
const spieler = await schieflage();
|
||||
for (let i = 0; i < 2; i += 1) {
|
||||
await credits.bookKulanz(spieler, ctx, { note: 'Nachträglich zugestanden' });
|
||||
}
|
||||
|
||||
const ergebnis = await characters.settleWithKulanz(spieler, ctx);
|
||||
|
||||
assert.equal(ergebnis.umgebucht, 2, 'zwei Kulanzen, zwei Übergänge');
|
||||
assert.equal(ergebnis.balances.REDESIGN_PAID, -1, 'das Minus schrumpft um zwei');
|
||||
assert.equal(ergebnis.balances.REDESIGN_KULANZ, 0, 'die Kulanz ist aufgebraucht');
|
||||
assert.equal(
|
||||
ergebnis.balances.redesignTotal,
|
||||
-1,
|
||||
'unterm Strich bleibt dasselbe - nur anders verteilt',
|
||||
);
|
||||
});
|
||||
|
||||
it('nimmt die ältesten Übergänge zuerst', async () => {
|
||||
const spieler = await schieflage();
|
||||
await credits.bookKulanz(spieler, ctx, { note: 'Eine Geste' });
|
||||
|
||||
await characters.settleWithKulanz(spieler, ctx);
|
||||
|
||||
const kulanzZeile = await db.prisma.creditLedger.findFirstOrThrow({
|
||||
where: { playerId: spieler, kind: 'REDESIGN_KULANZ', amount: { lt: 0 } },
|
||||
include: { character: { select: { name: true } } },
|
||||
});
|
||||
assert.equal(
|
||||
kulanzZeile.character?.name,
|
||||
'Stufe 2',
|
||||
'wer nachträglich Kulanz bekommt, bekommt sie für das Älteste',
|
||||
);
|
||||
});
|
||||
|
||||
it('meldet sich, wenn es nichts auszugleichen gibt', async () => {
|
||||
const spieler = await spielerMitPaketen('Im Plus', 1);
|
||||
await credits.bookKulanz(spieler, ctx, { note: 'Übrig' });
|
||||
|
||||
await assert.rejects(
|
||||
() => characters.settleWithKulanz(spieler, ctx),
|
||||
(error: Error & { code?: string }) => error.code === 'NOTHING_TO_SETTLE',
|
||||
);
|
||||
});
|
||||
|
||||
it('meldet sich, wenn keine Kulanz da ist', async () => {
|
||||
const spieler = await schieflage();
|
||||
|
||||
await assert.rejects(
|
||||
() => characters.settleWithKulanz(spieler, ctx),
|
||||
(error: Error & { code?: string }) => error.code === 'NOTHING_TO_SETTLE',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ein Redesign ausnahmsweise aus dem CC-Topf', () => {
|
||||
it('nimmt ein CC-Guthaben an', async () => {
|
||||
const spieler = await spielerMitPaketen('Ausnahmefall', 2);
|
||||
const eins = await characters.createCharacter(spieler, { name: 'Basis' }, ctx);
|
||||
|
||||
const vorher = await credits.getBalances(spieler);
|
||||
const zwei = await characters.redesignCharacter(
|
||||
eins.character.id,
|
||||
{ name: 'Aus dem CC-Topf', useKind: 'CC' },
|
||||
ctx,
|
||||
);
|
||||
|
||||
assert.equal(zwei.balances.CC, vorher.CC - 1, 'bezahlt aus dem CC-Topf');
|
||||
assert.equal(zwei.balances.REDESIGN_PAID, vorher.REDESIGN_PAID, 'die Redesigns bleiben');
|
||||
});
|
||||
|
||||
it('lässt sich vom Ausrichten an den Ketten nicht überschreiben', async () => {
|
||||
// Ohne Merker würde die Automatik beim nächsten Umsortieren daraus
|
||||
// wieder ein gewöhnliches Redesign machen.
|
||||
const spieler = await spielerMitPaketen('Bleibt so', 2);
|
||||
const eins = await characters.createCharacter(spieler, { name: 'Erstes' }, ctx);
|
||||
const zwei = await characters.redesignCharacter(
|
||||
eins.character.id,
|
||||
{ name: 'Zweites', useKind: 'CC' },
|
||||
ctx,
|
||||
);
|
||||
|
||||
await characters.rebuildFunding(spieler, ctx);
|
||||
|
||||
const buchung = await db.prisma.creditLedger.findFirst({
|
||||
where: { characterId: zwei.character.id, source: 'CONSUME', reversedBy: null },
|
||||
orderBy: { id: 'desc' },
|
||||
});
|
||||
assert.equal(buchung?.kind, 'CC', 'die bewusste Entscheidung bleibt stehen');
|
||||
});
|
||||
|
||||
it('merkt sich auch eine von Hand geänderte Bezahlart', async () => {
|
||||
const spieler = await spielerMitPaketen('Von Hand', 2);
|
||||
const eins = await characters.createCharacter(spieler, { name: 'A' }, ctx);
|
||||
const zwei = await characters.redesignCharacter(
|
||||
eins.character.id,
|
||||
{ name: 'B', useKind: 'REDESIGN_PAID' },
|
||||
ctx,
|
||||
);
|
||||
|
||||
await characters.changeFunding(zwei.character.id, 'CC', ctx);
|
||||
await characters.rebuildFunding(spieler, ctx);
|
||||
|
||||
const buchung = await db.prisma.creditLedger.findFirst({
|
||||
where: { characterId: zwei.character.id, source: 'CONSUME', reversedBy: null },
|
||||
orderBy: { id: 'desc' },
|
||||
});
|
||||
assert.equal(buchung?.kind, 'CC');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user