Feature: Periodischer Auto-Save (alle 10s, einstellbar) + Speichern bei Pause
Schließt die Lücke bei "hartem" Abbruch (Server-Stop/Absturz) ohne vorheriges Speicher-Ereignis. - main.js: zentrale Konstante AUTOSAVE_INTERVAL_SECONDS (Default 10); startAutoSave()/autoSave() speichern periodisch OHNE zu pausieren (überspringt bei Pause oder leerem Stand). togglePause() speichert zusätzlich beim Pausieren. - session.js: flushElapsedTime() akkumuliert die laufende Zeit, ohne die Session zu pausieren -> korrekte Zeitwerte beim Auto-Save während aktivem Training. - Resume-Route-Test ergänzt (update_progress -> /train stellt getippten Text + Cursor-Position wieder her). Testzahlen 96 -> 97. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2db844c4b4
commit
4a88414f82
6 changed files with 106 additions and 7 deletions
|
|
@ -97,7 +97,7 @@ typewriter/
|
|||
- `setup_database.py` - Datenbank-Setup
|
||||
|
||||
### 🔴 Tests (versioniert)
|
||||
- `tests/` - Unit-, Integration- & Route-Tests (96 Tests)
|
||||
- `tests/` - Unit-, Integration- & Route-Tests (97 Tests)
|
||||
|
||||
### ⚫ Temporär/Generiert (NICHT versioniert)
|
||||
- `venv/` - Virtual Environment
|
||||
|
|
|
|||
|
|
@ -204,16 +204,16 @@ Die Statistik-Seite (`/statistics`) bietet:
|
|||
|
||||
## 🧪 Tests
|
||||
|
||||
Das Projekt enthält eine umfassende Test-Suite mit **96 Tests** (100% Erfolgsrate) und lokaler Validierung per Pre-commit-Hook (ruff + Tests).
|
||||
Das Projekt enthält eine umfassende Test-Suite mit **97 Tests** (100% Erfolgsrate) und lokaler Validierung per Pre-commit-Hook (ruff + Tests).
|
||||
|
||||
### Test-Übersicht
|
||||
|
||||
| Kategorie | Anzahl | Status |
|
||||
|-----------|--------|--------|
|
||||
| **Gesamt** | 96 Tests | ✅ 100% |
|
||||
| **Gesamt** | 97 Tests | ✅ 100% |
|
||||
| **Unit Tests** | 54 Tests | ✅ Bestanden |
|
||||
| **Integration Tests** | 31 Tests | ✅ Bestanden |
|
||||
| **Route Tests** | 11 Tests | ✅ Bestanden |
|
||||
| **Route Tests** | 12 Tests | ✅ Bestanden |
|
||||
|
||||
### Test-Ausführung
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import { KeyboardHandler } from './keyboard.js';
|
|||
import { SessionManager } from './session.js';
|
||||
import { UIManager } from './ui.js';
|
||||
|
||||
// Auto-Save: Intervall in Sekunden. Zentraler Konfigwert – hier anpassbar.
|
||||
const AUTOSAVE_INTERVAL_SECONDS = 10;
|
||||
|
||||
class TypewriterApp {
|
||||
constructor() {
|
||||
// Application state
|
||||
|
|
@ -36,6 +39,8 @@ class TypewriterApp {
|
|||
|
||||
// Time display update interval
|
||||
this.timeDisplayInterval = null;
|
||||
// Auto-Save interval
|
||||
this.autoSaveInterval = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -76,6 +81,9 @@ class TypewriterApp {
|
|||
// Start time display update interval (updates every second)
|
||||
this.startTimeDisplayUpdate();
|
||||
|
||||
// Start periodic auto-save
|
||||
this.startAutoSave();
|
||||
|
||||
console.log('Typewriter Tutor initialized successfully');
|
||||
}
|
||||
|
||||
|
|
@ -372,6 +380,53 @@ class TypewriterApp {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the periodic auto-save interval.
|
||||
* Speichert den Fortschritt regelmäßig (alle AUTOSAVE_INTERVAL_SECONDS),
|
||||
* solange aktiv trainiert wird.
|
||||
*/
|
||||
startAutoSave() {
|
||||
if (this.autoSaveInterval) {
|
||||
clearInterval(this.autoSaveInterval);
|
||||
}
|
||||
this.autoSaveInterval = setInterval(
|
||||
() => this.autoSave(),
|
||||
AUTOSAVE_INTERVAL_SECONDS * 1000
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the periodic auto-save interval.
|
||||
*/
|
||||
stopAutoSave() {
|
||||
if (this.autoSaveInterval) {
|
||||
clearInterval(this.autoSaveInterval);
|
||||
this.autoSaveInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Periodischer Auto-Save: speichert den aktuellen Stand, OHNE das
|
||||
* Training zu unterbrechen. Überspringt, wenn pausiert oder noch nichts
|
||||
* getippt wurde.
|
||||
*/
|
||||
async autoSave() {
|
||||
if (this.sessionManager.isPaused) return;
|
||||
if (this.currentCharIndex === 0 && this.userInput === '') return;
|
||||
|
||||
// Verstrichene Zeit aktualisieren, ohne zu pausieren
|
||||
this.sessionManager.flushElapsedTime();
|
||||
try {
|
||||
await this.sessionManager.saveProgress(
|
||||
this.userInput,
|
||||
this.currentCharIndex,
|
||||
this.fullText
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Auto-Save fehlgeschlagen:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update progress bar based on current position in lesson
|
||||
*/
|
||||
|
|
@ -451,6 +506,13 @@ class TypewriterApp {
|
|||
|
||||
if (this.sessionManager.isPaused) {
|
||||
this.metronomePlayer.stop();
|
||||
// Beim Pausieren den aktuellen Stand speichern (Zeit ist durch
|
||||
// togglePause bereits geflusht).
|
||||
this.sessionManager.saveProgress(
|
||||
this.userInput,
|
||||
this.currentCharIndex,
|
||||
this.fullText
|
||||
).catch(error => console.error('Speichern bei Pause fehlgeschlagen:', error));
|
||||
} else {
|
||||
if (this.metronomePlayer.enabled) {
|
||||
this.metronomePlayer.start();
|
||||
|
|
|
|||
|
|
@ -83,6 +83,24 @@ export class SessionManager {
|
|||
this.lastStartTime = new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* Akkumuliert die bisher verstrichene Zeit in totalElapsedTime /
|
||||
* netTrainingTime, OHNE die Session zu pausieren. Wird vom periodischen
|
||||
* Auto-Save genutzt, damit der gespeicherte Zeitwert aktuell ist, während
|
||||
* das Training ununterbrochen weiterläuft.
|
||||
*/
|
||||
flushElapsedTime() {
|
||||
if (this.lastStartTime !== null) {
|
||||
const now = new Date();
|
||||
const delta = now - this.lastStartTime;
|
||||
this.totalElapsedTime += delta;
|
||||
if (!this.isPaused) {
|
||||
this.netTrainingTime += delta;
|
||||
}
|
||||
this.lastStartTime = now; // Zeitmessung läuft weiter
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle pause state
|
||||
* @returns {boolean} New pause state
|
||||
|
|
|
|||
|
|
@ -27,13 +27,13 @@ Dieses Verzeichnis enthält Unit Tests für die Service-Layer der Typewriter Tra
|
|||
|
||||
| Bereich | Test-Datei | Anzahl Tests | Status |
|
||||
|---------|-----------|--------------|--------|
|
||||
| **Routen & API** | `test_routes.py` | 11 | ✅ Alle bestanden |
|
||||
| **Routen & API** | `test_routes.py` | 12 | ✅ Alle bestanden |
|
||||
|
||||
**Gesamt Route Tests:** 11 Tests, alle bestanden ✅
|
||||
**Gesamt Route Tests:** 12 Tests, alle bestanden ✅
|
||||
|
||||
### Gesamtübersicht
|
||||
|
||||
**Gesamt:** 96 Tests (54 Unit + 31 Integration + 11 Route), **alle bestanden** ✅
|
||||
**Gesamt:** 97 Tests (54 Unit + 31 Integration + 12 Route), **alle bestanden** ✅
|
||||
|
||||
### Hinweise
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,25 @@ class TestTrainingApi(RouteTestBase):
|
|||
})
|
||||
self.assertEqual(r.status_code, 400)
|
||||
|
||||
def test_progress_persists_and_restores(self):
|
||||
# Teilfortschritt speichern (so wie es der Auto-Save / beforeunload tut)
|
||||
r = self.client.post('/update_progress', json={
|
||||
'user_input': 'ffff jj',
|
||||
'current_text': 'ffff jjjj',
|
||||
'elapsed_time': 8.0,
|
||||
'cursor_position': 7,
|
||||
'total_elapsed_time': 8000,
|
||||
'net_training_time': 8000,
|
||||
'key_stroke_count': 7,
|
||||
'is_paused': False,
|
||||
})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# /train erneut laden -> Stand (getippter Text + Position) wiederhergestellt
|
||||
html = self.client.get('/train').get_data(as_text=True)
|
||||
self.assertIn('window.lastText = "ffff jj"', html)
|
||||
self.assertIn('window.currentCharIndex = 7', html)
|
||||
|
||||
|
||||
class TestStatisticsApi(RouteTestBase):
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue