import 'package:flutter_test/flutter_test.dart'; import 'package:spotiflac_android/utils/lyrics_parser.dart'; import 'package:spotiflac_android/utils/lyrics_timeline.dart'; void main() { test( 'backing vocals do not dim an unlabelled lead that is still singing', () { final lines = LyricsParser.parse(''' [00:01.00]<00:01.00>Main<00:05.00> [bg:<00:02.00>Echo<00:03.00>] ''').lines; expect(activeLyricIndices(lines, const Duration(seconds: 2), 1), {0, 1}); expect(activeLyricIndices(lines, const Duration(seconds: 4), 1), {0}); }, ); test( 'overlapping singers stay active until their ends, including seek back', () { final lines = LyricsParser.parse(''' [00:01.00]v1:<00:01.00>Lead<00:05.00> [00:02.00]v2:<00:02.00>Guest<00:04.00> [00:06.00]v3:Third ''').lines; Set active(int seconds) { final position = Duration(seconds: seconds); return activeLyricIndices( lines, position, LyricsParser.activeIndex(lines, position), ); } expect(active(0), isEmpty); expect(active(1), {0}); expect(active(3), {0, 1}); expect(active(4), {0}); expect(active(5), isEmpty); expect(active(6), {2}); expect(active(3), {0, 1}); expect(active(1), {0}); }, ); List<(int, int)> gaps(String text) => lyricsTimelineWithGaps(LyricsParser.parse(text).lines) .where((line) => line.text.isEmpty) .map((line) => (line.time.inMilliseconds, line.end!.inMilliseconds)) .toList(); test('intro and explicit LRC breaks count down, never the outro', () { const text = ''' [00:00.00] [00:09.00]First [00:12.00] [00:14.00] [00:21.00]Second [00:25.00] [00:30.00] '''; expect(gaps(text), [(0, 9000), (12000, 21000)]); expect( lyricsTimelineWithGaps(LyricsParser.parse(text).lines).last.text, 'Second', ); }); test('TTML line ends identify breaks without empty timestamps', () { expect( gaps('''

First

Second

Third

'''), [(0, 9000), (12000, 21000)], ); }); test('enhanced LRC word ends retain held vocals before a break', () { expect( gaps(''' [00:00.00]<00:00.00>Held<00:16.00> [00:20.00]<00:20.00>Last<00:24.00> '''), [(16000, 20000)], ); }); test('overlapping lines cannot start a countdown during a held vocal', () { expect( gaps('''

Held

Backing vocal

Last

'''), [(16000, 20000)], ); }); test('plain LRC does not invent an end time from distance between lines', () { expect(gaps('[00:00]Held\n[00:30]Last'), isEmpty); expect(gaps('[00:00]\n[00:30]'), isEmpty); expect(gaps('Unsynced text'), isEmpty); }); test('short rests and immediate vocals have no flashing countdown', () { expect(gaps('[00:02]First\n[00:04]\n[00:06]Last'), isEmpty); expect(gaps('[00:00]First\n[00:04]\n[00:07]Last'), [(4000, 7000)]); }); test('offset correction also moves the countdown boundaries', () { expect(gaps('[offset:500]\n[00:09]First\n[00:12]\n[00:21]Last'), [ (0, 8500), (11500, 20500), ]); }); test('word ends protect against an early or invalid paragraph end', () { expect( gaps('''

Held

Last

'''), [(8000, 12000)], ); expect( gaps('''

Unknown end

Last

'''), isEmpty, ); }); }