From 367533f1f5994749a8a0dea00a3ca522fe5fed99 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Sun, 2 Aug 2026 09:22:39 +0200 Subject: [PATCH] fix: make cash flow amounts fully accessible --- .../src/app/models/transaction-amount.spec.ts | 50 ++++++++++++++----- .../src/app/models/transaction-amount.ts | 10 ---- .../transaction-amount.html | 4 +- .../transaction-amount.scss | 13 +++++ .../transaction-amount.spec.ts | 25 +++++++--- .../transaction-amount/transaction-amount.ts | 6 +-- 6 files changed, 73 insertions(+), 35 deletions(-) diff --git a/myteamwallet_frontend_modern/src/app/models/transaction-amount.spec.ts b/myteamwallet_frontend_modern/src/app/models/transaction-amount.spec.ts index 737ee76..71af012 100644 --- a/myteamwallet_frontend_modern/src/app/models/transaction-amount.spec.ts +++ b/myteamwallet_frontend_modern/src/app/models/transaction-amount.spec.ts @@ -1,4 +1,4 @@ -import { presentCashFlow, signedTransactionAmount } from './transaction-amount'; +import { presentCashFlow } from './transaction-amount'; describe('presentCashFlow', () => { it.each([ @@ -32,12 +32,48 @@ describe('presentCashFlow', () => { context: 'player' as const, expected: { direction: 'neutral', amount: 7, sign: '' }, }, + { + amount: 6, + type: 'credit', + context: 'player' as const, + expected: { direction: 'neutral', amount: 6, sign: '' }, + }, + { + amount: 5, + type: 'levy', + context: 'player' as const, + expected: { direction: 'neutral', amount: 5, sign: '' }, + }, + { + amount: 4, + type: 'fee', + context: 'player' as const, + expected: { direction: 'neutral', amount: 4, sign: '' }, + }, + { + amount: 3, + type: 'payment', + context: 'team' as const, + expected: { direction: 'neutral', amount: 3, sign: '' }, + }, + { + amount: 2, + type: 'expense', + context: 'player' as const, + expected: { direction: 'neutral', amount: 2, sign: '' }, + }, { amount: -7, type: 'unknown', context: 'player' as const, expected: { direction: 'neutral', amount: 7, sign: '' }, }, + { + amount: -9, + type: 'unknown', + context: 'team' as const, + expected: { direction: 'neutral', amount: 9, sign: '' }, + }, ])( 'presents $context $type transactions with their cash-flow direction', ({ amount, type, context, expected }) => { @@ -45,15 +81,3 @@ describe('presentCashFlow', () => { }, ); }); - -describe('signedTransactionAmount', () => { - it('shows debit transaction types as negative amounts', () => { - expect(signedTransactionAmount(12, 'fine')).toBe(-12); - expect(signedTransactionAmount(12, { id: 14, name: 'expense' })).toBe(-12); - }); - - it('keeps credits and negative reversal amounts unchanged', () => { - expect(signedTransactionAmount(12, 'credit')).toBe(12); - expect(signedTransactionAmount(-12, { id: 1, name: 'credit' })).toBe(-12); - }); -}); diff --git a/myteamwallet_frontend_modern/src/app/models/transaction-amount.ts b/myteamwallet_frontend_modern/src/app/models/transaction-amount.ts index 4ce424d..6915b38 100644 --- a/myteamwallet_frontend_modern/src/app/models/transaction-amount.ts +++ b/myteamwallet_frontend_modern/src/app/models/transaction-amount.ts @@ -10,16 +10,6 @@ export interface CashFlowPresentation { sign: '+' | '−' | ''; } -const debitTypeNames = new Set(['fine', 'levy', 'fee', 'expense']); - -export function signedTransactionAmount(amount: number, type: TransactionTypeLike): number { - const typeId = typeof type === 'number' ? type : typeof type === 'object' ? type?.id : undefined; - const typeName = - typeof type === 'string' ? type : typeof type === 'object' ? type?.name : undefined; - const isDebit = (typeId ?? 0) > 10 || debitTypeNames.has(typeName ?? ''); - return isDebit ? -Math.abs(amount) : amount; -} - export function presentCashFlow( amount: number, type: TransactionTypeLike, diff --git a/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.html b/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.html index b917de6..6af2b27 100644 --- a/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.html +++ b/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.html @@ -2,8 +2,8 @@ data-testid="transaction-amount" class="transaction-amount" [class]="presentation().direction" - [attr.aria-label]="directionLabel()" > - {{ presentation().sign }} + {{ directionLabel() }} + {{ presentation().amount | currency: 'EUR' }} diff --git a/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.scss b/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.scss index 0d0774c..f9d4081 100644 --- a/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.scss +++ b/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.scss @@ -1,5 +1,6 @@ .transaction-amount { font-variant-numeric: tabular-nums; + white-space: nowrap; &.inflow { color: var(--mat-sys-primary); @@ -14,6 +15,18 @@ } } +.transaction-amount__direction { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + .transaction-amount__sign { display: inline-block; min-width: 0.8ch; diff --git a/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.spec.ts b/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.spec.ts index e6cfc3a..fdfb9db 100644 --- a/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.spec.ts +++ b/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.spec.ts @@ -9,7 +9,8 @@ describe('TransactionAmount', () => { context: 'player' as const, sign: '+', semanticClass: 'inflow', - label: 'Einnahme', + direction: 'Einzahlung', + text: 'Einzahlung 12,50 \u20ac', }, { amount: 12.5, @@ -17,7 +18,8 @@ describe('TransactionAmount', () => { context: 'team' as const, sign: '−', semanticClass: 'outflow', - label: 'Ausgabe', + direction: 'Auszahlung', + text: 'Auszahlung 12,50 \u20ac', }, { amount: 12.5, @@ -25,11 +27,12 @@ describe('TransactionAmount', () => { context: 'player' as const, sign: '', semanticClass: 'neutral', - label: 'Neutraler Betrag', + direction: 'Keine Kassenbewegung', + text: 'Keine Kassenbewegung 12,50 \u20ac', }, ])( 'renders the $semanticClass cash-flow meaning accessibly', - async ({ amount, type, context, sign, semanticClass, label }) => { + async ({ amount, type, context, sign, semanticClass, direction, text }) => { await TestBed.configureTestingModule({ imports: [TransactionAmount] }).compileComponents(); const fixture = TestBed.createComponent(TransactionAmount); fixture.componentRef.setInput('amount', amount); @@ -39,11 +42,19 @@ describe('TransactionAmount', () => { const element = fixture.nativeElement.querySelector('[data-testid="transaction-amount"]'); const signElement = element.querySelector('.transaction-amount__sign'); + const directionElement = element.querySelector('.transaction-amount__direction'); + const accessibleElement = element.cloneNode(true); + accessibleElement + .querySelectorAll('[aria-hidden=true]') + .forEach((node: Element) => node.remove()); expect(element.classList).toContain(semanticClass); expect(signElement.textContent).toBe(sign); - expect(element.textContent).toContain('12,50'); - expect(element.textContent).toContain('€'); - expect(element.getAttribute('aria-label')).toBe(label); + expect(signElement.getAttribute('aria-hidden')).toBe('true'); + expect(directionElement.textContent).toBe(direction); + expect(accessibleElement.textContent.replace(/\s+/g, ' ').trim()).toBe(text); + expect(element.getAttribute('aria-label')).toBeNull(); + expect(getComputedStyle(element).whiteSpace).toBe('nowrap'); + expect(getComputedStyle(directionElement).position).toBe('absolute'); }, ); }); diff --git a/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.ts b/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.ts index 7ee87be..542e2db 100644 --- a/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.ts +++ b/myteamwallet_frontend_modern/src/app/shared/transaction-amount/transaction-amount.ts @@ -28,9 +28,9 @@ export class TransactionAmount { protected readonly directionLabel = computed( () => ({ - inflow: 'Einnahme', - outflow: 'Ausgabe', - neutral: 'Neutraler Betrag', + inflow: 'Einzahlung', + outflow: 'Auszahlung', + neutral: 'Keine Kassenbewegung', })[this.presentation().direction], ); }