mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 04:42:02 +02:00
199 lines
5.6 KiB
Dart
199 lines
5.6 KiB
Dart
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 stays under its lead without changing the timing order', () {
|
|
final lines = LyricsParser.parse('''
|
|
[00:01.00]v1:<00:01.00>Lead<00:06.00>
|
|
[bg:<00:04.00>Echo<00:06.00>]
|
|
[bg:<00:05.00>Again<00:06.00>]
|
|
[00:03.00]v2:<00:03.00>Guest<00:06.00>
|
|
''').lines;
|
|
final layout = LyricDisplayLayout(lines);
|
|
expect(lines.map((line) => line.text), ['Lead', 'Guest', 'Echo', 'Again']);
|
|
expect(layout.lineOrder.map((i) => lines[i].text), [
|
|
'Lead',
|
|
'Echo',
|
|
'Again',
|
|
'Guest',
|
|
]);
|
|
expect(layout.rowForLine, [0, 3, 1, 2]);
|
|
expect(layout.leadForLine, [0, 1, 0, 0]);
|
|
expect(layout.focusForLine, [0, 1, 1, 1]);
|
|
expect(activeLyricIndices(lines, const Duration(seconds: 5), 3), {
|
|
0,
|
|
1,
|
|
2,
|
|
3,
|
|
});
|
|
});
|
|
|
|
test('background onset keeps focus on the lead and gaps remain separate', () {
|
|
final lines = lyricsTimelineWithGaps(
|
|
LyricsParser.parse('''
|
|
[00:05.00]v2:<00:05.00>Lead<00:10.00>
|
|
[bg:<00:06.00>Echo<00:11.00>]
|
|
[00:15.00]Next
|
|
''').lines,
|
|
);
|
|
final layout = LyricDisplayLayout(lines);
|
|
expect(lines.map((line) => line.text), ['', 'Lead', 'Echo', '', 'Next']);
|
|
expect(layout.lineOrder, [0, 1, 2, 3, 4]);
|
|
expect(layout.focusForLine, [0, 1, 1, 3, 4]);
|
|
expect(lines[3].time, const Duration(seconds: 11));
|
|
});
|
|
|
|
test(
|
|
'a backing pickup can start before the lead without losing its parent',
|
|
() {
|
|
final lines = LyricsParser.parse('''
|
|
<tt xmlns="http://www.w3.org/ns/ttml" xmlns:m="http://www.w3.org/ns/ttml#metadata">
|
|
<body><p begin="1s" end="6s" m:agent="v2"><span m:role="x-bg" begin="1s" end="5s">Pickup</span><span begin="2s" end="6s">Lead</span></p></body></tt>
|
|
''').lines;
|
|
final layout = LyricDisplayLayout(lines);
|
|
expect(lines.map((line) => line.text), ['Pickup', 'Lead']);
|
|
expect(layout.lineOrder, [1, 0]);
|
|
expect(layout.focusForLine, [1, 1]);
|
|
},
|
|
);
|
|
|
|
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<int> 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('''
|
|
<tt><body><div>
|
|
<p begin="9s" end="12s">First</p>
|
|
<p begin="21s" end="24s">Second</p>
|
|
<p begin="25s" end="28s">Third</p>
|
|
</div></body></tt>
|
|
'''),
|
|
[(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('''
|
|
<tt><body><div>
|
|
<p begin="0s" end="16s">Held</p>
|
|
<p begin="8s" end="10s">Backing vocal</p>
|
|
<p begin="20s" end="24s">Last</p>
|
|
</div></body></tt>
|
|
'''),
|
|
[(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('''
|
|
<tt><body><div>
|
|
<p begin="0s" end="2s"><span begin="0s" end="8s">Held</span></p>
|
|
<p begin="12s" end="14s">Last</p>
|
|
</div></body></tt>
|
|
'''),
|
|
[(8000, 12000)],
|
|
);
|
|
expect(
|
|
gaps('''
|
|
<tt><body><div>
|
|
<p begin="0s" end="2s"><span begin="5s">Unknown end</span></p>
|
|
<p begin="12s" end="14s">Last</p>
|
|
</div></body></tt>
|
|
'''),
|
|
isEmpty,
|
|
);
|
|
});
|
|
}
|