import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:melo_app/models/song.dart'; import 'package:melo_app/services/lyrics_service.dart'; void main() { // Blockiert echte HTTP-Requests im Test (Netease-Fallback → 400 → null) TestWidgetsFlutterBinding.ensureInitialized(); group('parseLrc', () { test('parst einfache [mm:ss.xx]-Zeilen', () { const lrc = '[00:12.34]Erste Zeile\n[00:45.67]Zweite Zeile\n'; final zeilen = LyricsService.parseLrc(lrc); expect(zeilen.length, 2); expect(zeilen[0].zeit, const Duration(milliseconds: 12340)); expect(zeilen[0].text, 'Erste Zeile'); expect(zeilen[1].zeit, const Duration(milliseconds: 45670)); expect(zeilen[1].text, 'Zweite Zeile'); }); test('unterstützt [mm:ss] ohne Millisekunden', () { const lrc = '[01:05]Nur Sekunden\n'; final zeilen = LyricsService.parseLrc(lrc); expect(zeilen.length, 1); expect(zeilen[0].zeit, const Duration(minutes: 1, seconds: 5)); }); test('unterstützt mehrere Zeitstempel pro Zeile', () { const lrc = '[00:10.00][00:20.00][00:30.00]Refrain\n'; final zeilen = LyricsService.parseLrc(lrc); expect(zeilen.length, 3); expect(zeilen.map((z) => z.text).toSet(), {'Refrain'}); expect(zeilen[0].zeit, const Duration(seconds: 10)); expect(zeilen[1].zeit, const Duration(seconds: 20)); expect(zeilen[2].zeit, const Duration(seconds: 30)); }); test('ignoriert Meta-Tags und wertet [offset:] aus', () { const lrc = '[ti:Testtitel]\n[ar:Testkünstler]\n[offset:500]\n' '[00:10.00]Zeile mit Offset\n'; final zeilen = LyricsService.parseLrc(lrc); expect(zeilen.length, 1); expect(zeilen[0].zeit, const Duration(milliseconds: 10500)); }); test('sortiert Zeilen nach Zeitstempel', () { const lrc = '[00:30.00]Spät\n[00:10.00]Früh\n[00:20.00]Mitte\n'; final zeilen = LyricsService.parseLrc(lrc); expect(zeilen.map((z) => z.text).toList(), ['Früh', 'Mitte', 'Spät']); }); test('entfernt Karaoke-Tags <...>', () { const lrc = '[00:10.00]<00:10.00>Hal<00:11.00>lo\n'; final zeilen = LyricsService.parseLrc(lrc); expect(zeilen.length, 1); expect(zeilen[0].text, 'Hallo'); }); test('leere Eingabe liefert leere Liste', () { expect(LyricsService.parseLrc(''), isEmpty); expect(LyricsService.parseLrc('nur Text ohne Zeitstempel'), isEmpty); }); }); group('indexFuerPosition', () { final zeilen = LyricsService.parseLrc( '[00:10.00]Eins\n[00:20.00]Zwei\n[00:30.00]Drei\n', ); test('vor der ersten Zeile → 0', () { expect( LyricsService.indexFuerPosition(const Duration(seconds: 5), zeilen), 0, ); }); test('genau auf einer Zeile → deren Index', () { expect( LyricsService.indexFuerPosition(const Duration(seconds: 20), zeilen), 1, ); }); test('zwischen zwei Zeilen → vorherige', () { expect( LyricsService.indexFuerPosition(const Duration(seconds: 25), zeilen), 1, ); }); test('nach der letzten Zeile → letzter Index', () { expect( LyricsService.indexFuerPosition(const Duration(seconds: 99), zeilen), 2, ); }); test('leere Liste → -1', () { expect(LyricsService.indexFuerPosition(Duration.zero, []), -1); }); }); group('ladeLyrics (lokale .lrc)', () { test('liest .lrc-Datei neben dem Song', () async { final dir = await Directory.systemTemp.createTemp('melo_lrc_test'); addTearDown(() => dir.delete(recursive: true)); final songDatei = '${dir.path}/test.mp3'; final lrcDatei = '${dir.path}/test.lrc'; File(songDatei).writeAsStringSync('dummy'); File(lrcDatei).writeAsStringSync('[00:05.00]Lokal\n[00:10.00]Zeile\n'); final song = Song( titel: 'Test', kuenstler: 'K', dauerSekunden: 60, dateiPfad: songDatei, ); final zeilen = await LyricsService().ladeLyrics(song); expect(zeilen, isNotNull); expect(zeilen!.length, 2); expect(zeilen.first.text, 'Lokal'); }); test('ohne .lrc-Datei → null', () async { final dir = await Directory.systemTemp.createTemp('melo_lrc_test2'); addTearDown(() => dir.delete(recursive: true)); final song = Song( titel: 'Test', kuenstler: 'K', dauerSekunden: 60, dateiPfad: '${dir.path}/ohne.mp3', ); // Keine .lrc-Datei anlegen → Netease wird nicht getroffen (kein Netz im Test) final zeilen = await LyricsService().ladeLyrics(song); expect(zeilen, isNull); }); }); }