Finanzmanager/tests/e2e/specs/rechner.spec.js
fds2610-bot d1db40fa55 Fix #126: Taschenrechner an den Betragsfeldern
Jedes Zahlenfeld (inputmode="decimal") zeigt rechts ein Rechnersymbol.
Klick darauf oder Alt+R öffnet einen Taschenrechner mit Grundrechenarten
und Klammern, komplett per Tastatur bedienbar: Ziffern, + - * / ( ),
Backspace, Entf, Pfeiltasten, C löscht alles, = setzt das Ergebnis ein,
Enter überträgt ins Feld, Escape schließt und fokussiert das Feld. Das
Ergebnis steht nach jedem Tastendruck unter der Rechnung.

Das Symbol ist ein Hintergrundbild (kein Wrapper-Element, kein Eingriff in
die Layouts); gerechnet wird mit eigenem Parser statt eval. In modalen
Dialogen wandert der Rechner mit hinein. Stück/Kurs übernehmen 6,
Zinssätze 3 Nachkommastellen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:22:18 +02:00

146 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @ts-check
const { test, expect } = require('@playwright/test')
const BENUTZER = process.env.E2E_USER || 'e2e'
const PASSWORT = process.env.E2E_PASSWORD || 'e2e-testpasswort'
async function anmelden(page) {
await page.goto('/login')
await page.fill('#benutzername', BENUTZER)
await page.fill('#passwort', PASSWORT)
await page.click('button[type="submit"]')
await expect(page).toHaveURL(/\/$/)
}
/* Klick auf das Rechnersymbol – es sitzt rechts im Feld. */
async function symbolKlicken(feld) {
const box = await feld.boundingBox()
if (!box) throw new Error('Feld nicht sichtbar')
await feld.page().mouse.click(box.x + box.width - 12, box.y + box.height / 2)
}
test.describe('Taschenrechner an Betragsfeldern (#126)', () => {
test.beforeEach(async ({ page }) => {
await anmelden(page)
})
test('Symbol im Feld öffnet den Rechner, Enter überträgt das Ergebnis', async ({ page }) => {
await page.goto('/konten/neu')
const feld = page.locator('#anfangssaldo')
await feld.fill('')
const bild = await feld.evaluate((el) => getComputedStyle(el).backgroundImage)
expect(bild).toContain('data:image/svg+xml')
await symbolKlicken(feld)
const rechner = page.locator('.rechner')
await expect(rechner).toBeVisible()
const eingabe = rechner.locator('.rechner-eingabe')
const ergebnis = rechner.locator('.rechner-ergebnis')
await expect(eingabe).toBeFocused()
// Am Feld, rechtsbündig – darunter oder, fehlt dort der Platz, darüber.
const feldBox = await feld.boundingBox()
const rechnerBox = await rechner.boundingBox()
if (!feldBox || !rechnerBox) throw new Error('nicht sichtbar')
const darunter = rechnerBox.y - (feldBox.y + feldBox.height)
const darueber = feldBox.y - (rechnerBox.y + rechnerBox.height)
expect((darunter >= 0 && darunter < 20) || (darueber >= 0 && darueber < 20)).toBe(true)
expect(Math.abs(rechnerBox.x + rechnerBox.width - (feldBox.x + feldBox.width))).toBeLessThan(2)
// Ergebnis nach jedem Tastendruck, Punkt vor Strich, Klammern.
await page.keyboard.type('12,5*2')
await expect(ergebnis).toHaveText('25')
await page.keyboard.type('+(3-1)/4')
await expect(eingabe).toHaveValue('12,5×2+(3−1)÷4')
await expect(ergebnis).toHaveText('25,5')
// Unvollständig: das letzte Ergebnis bleibt gedämpft stehen.
await page.keyboard.type('+')
await expect(ergebnis).toHaveClass(/veraltet/)
await expect(ergebnis).toHaveText('25,5')
// Buchstaben gehören nicht in eine Rechnung.
await page.keyboard.type('q')
await expect(eingabe).toHaveValue('12,5×2+(3−1)÷4+')
// Backspace links, Entf rechts der Schreibmarke, Pfeile bewegen sie.
await page.keyboard.press('Backspace')
await page.keyboard.press('ArrowLeft')
await page.keyboard.press('ArrowLeft')
await page.keyboard.press('Delete')
await expect(eingabe).toHaveValue('12,5×2+(3−1)4')
await expect(ergebnis).toHaveClass(/fehler/)
await page.keyboard.type('*')
await expect(eingabe).toHaveValue('12,5×2+(3−1)×4')
await expect(ergebnis).toHaveText('33')
await page.keyboard.press('Enter')
await expect(rechner).toBeHidden()
await expect(feld).toHaveValue('33,00')
await expect(feld).toBeFocused()
})
test('C löscht, Escape schließt ohne Übernahme, Alt+R öffnet mit dem Feldwert', async ({ page }) => {
await page.goto('/konten/neu')
const feld = page.locator('#anfangssaldo')
await feld.fill('1.234,50')
await feld.focus()
await page.keyboard.press('Alt+r')
const rechner = page.locator('.rechner')
const eingabe = rechner.locator('.rechner-eingabe')
await expect(rechner).toBeVisible()
await expect(eingabe).toHaveValue('1234,5')
await page.keyboard.press('c')
await expect(eingabe).toHaveValue('')
await page.keyboard.type('7/0')
await expect(rechner.locator('.rechner-ergebnis')).toHaveText('Division durch 0')
await page.keyboard.press('Escape')
await expect(rechner).toBeHidden()
await expect(feld).toBeFocused()
await expect(feld).toHaveValue('1.234,50')
})
test('Tasten lassen sich auch anklicken', async ({ page }) => {
await page.goto('/konten/neu')
const feld = page.locator('#kreditlimit')
await feld.fill('')
await symbolKlicken(feld)
const rechner = page.locator('.rechner')
for (const taste of ['9', '÷', '4', '=']) {
await rechner.locator(`button[data-taste="${taste}"]`).click()
}
await expect(rechner.locator('.rechner-eingabe')).toHaveValue('2,25')
await expect(rechner.locator('.rechner-eingabe')).toBeFocused()
await rechner.getByRole('button', { name: 'Übernehmen' }).click()
await expect(feld).toHaveValue('2,25')
})
test('Im modalen Dialog: Escape schließt nur den Rechner', async ({ page }) => {
await page.goto('/vorlagen')
await page.click('#neu-knopf')
const dialog = page.locator('#neudialog')
await expect(dialog).toBeVisible()
const feld = dialog.locator('#betrag')
await feld.fill('')
await symbolKlicken(feld)
const rechner = dialog.locator('.rechner')
await expect(rechner).toBeVisible()
await page.keyboard.type('19,99*3')
await page.keyboard.press('Escape')
await expect(rechner).toBeHidden()
await expect(dialog).toBeVisible()
await symbolKlicken(feld)
await page.keyboard.type('19,99*3')
await page.keyboard.press('Enter')
await expect(dialog).toBeVisible()
await expect(feld).toHaveValue('59,97')
})
})