Files
SpotiFLAC-Mobile/test/synced_lyrics_scroll_test.dart
T

209 lines
6.4 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/utils/synced_lyrics_scroll.dart';
void main() {
test('symmetric padding lets the final lyric line reach center', () {
const viewport = 500.0;
const lineExtent = 64.0;
const lineCount = 20;
final padding = syncedLyricsCenterPadding(
viewportDimension: viewport,
estimatedLineExtent: lineExtent,
);
final contentExtent = (padding * 2) + (lineCount * lineExtent);
final maxScrollExtent = contentExtent - viewport;
final lastLineOffset = syncedLyricsEstimatedOffset(
index: lineCount - 1,
estimatedLineExtent: lineExtent,
);
expect(padding, 218);
expect(lastLineOffset, maxScrollExtent);
});
test('small viewports retain minimum breathing room', () {
expect(
syncedLyricsCenterPadding(viewportDimension: 80, estimatedLineExtent: 64),
24,
);
});
test('advances at the exact next line boundary', () {
const starts = [
Duration(seconds: 1),
Duration(seconds: 3),
Duration(seconds: 3),
Duration(seconds: 6),
];
expect(
syncedLyricsDueLineIndex(
lineStarts: starts,
currentIndex: 0,
position: const Duration(milliseconds: 2999),
),
0,
);
expect(
syncedLyricsDueLineIndex(
lineStarts: starts,
currentIndex: 0,
position: const Duration(seconds: 3),
),
2,
);
});
group('smooth timed lyric highlight', () {
double liftAt(int position, {int end = 1500}) => syncedLyricSegmentLift(
position: Duration(milliseconds: position),
start: const Duration(seconds: 1),
end: Duration(milliseconds: end),
);
test('short words anticipate gently and remain raised after singing', () {
expect(liftAt(800), 0);
expect(liftAt(950), inExclusiveRange(0, 1));
expect(liftAt(1000), greaterThan(liftAt(950)));
expect(liftAt(1000), lessThan(0.2));
expect(liftAt(1200), inExclusiveRange(0.4, 0.7));
expect(liftAt(1500), lessThan(1));
expect(liftAt(1540), 1);
expect(liftAt(3000), 1);
});
test('held words rise further then settle to the normal highlight', () {
final peak = liftAt(2300, end: 4000);
expect(liftAt(1200, end: 4000), inExclusiveRange(0.4, 0.7));
expect(peak, greaterThan(1));
expect(liftAt(3900, end: 4000), inExclusiveRange(1, peak));
expect(liftAt(4000, end: 4000), 1);
expect(liftAt(4200, end: 4000), 1);
expect(liftAt(2300, end: 4000), peak);
expect(liftAt(800, end: 4000), 0);
});
test('short syllables keep the slow rise and trail their color sweep', () {
expect(liftAt(1100, end: 1100), lessThan(0.4));
expect(liftAt(1200, end: 1100), lessThan(0.7));
expect(liftAt(1540, end: 1100), 1);
for (var position = 820; position < 1540; position += 16) {
final before = liftAt(position, end: 1100);
final after = liftAt(position + 16, end: 1100);
expect(after - before, inInclusiveRange(0, 0.034));
}
});
test('invalid or zero-length timing does not move the text', () {
expect(liftAt(1200, end: 1000), 0);
expect(liftAt(1200, end: 500), 0);
});
test('graphemes rise in sweep order and converge after the word', () {
double glyph(int time, double offset) => syncedLyricSegmentLift(
position: Duration(milliseconds: time),
start: const Duration(seconds: 1),
end: const Duration(milliseconds: 1500),
progressOffset: offset,
);
expect(glyph(1300, 0), greaterThan(glyph(1300, 0.33)));
expect(glyph(1300, 0.33), greaterThan(glyph(1300, 0.66)));
expect(glyph(1500, 0.75), lessThan(1));
expect(glyph(2100, 0), 1);
expect(glyph(2100, 0.75), 1);
for (var time = 800; time < 2100; time += 16) {
final change = glyph(time + 16, 0.75) - glyph(time, 0.75);
expect(change, inInclusiveRange(0, 0.034));
}
final held = syncedLyricSegmentLift(
position: const Duration(milliseconds: 5500),
start: const Duration(seconds: 3),
end: const Duration(seconds: 6),
progressOffset: 0.75,
);
expect(held, greaterThan(1));
});
test('interpolates position only while playback is advancing', () {
expect(
interpolatedSyncedLyricsPosition(
anchorPosition: const Duration(seconds: 10),
elapsedSinceAnchor: const Duration(milliseconds: 250),
isPlaying: true,
),
const Duration(milliseconds: 10250),
);
expect(
interpolatedSyncedLyricsPosition(
anchorPosition: const Duration(seconds: 10),
elapsedSinceAnchor: const Duration(milliseconds: 250),
isPlaying: false,
),
const Duration(seconds: 10),
);
});
test('blends small clock corrections and applies seeks immediately', () {
expect(
reconcileSyncedLyricsPosition(
predictedPosition: const Duration(milliseconds: 1000),
reportedPosition: const Duration(milliseconds: 1100),
),
const Duration(milliseconds: 1025),
);
expect(
reconcileSyncedLyricsPosition(
predictedPosition: const Duration(seconds: 1),
reportedPosition: const Duration(seconds: 5),
),
const Duration(seconds: 5),
);
});
test('calculates continuous progress inside a timed segment', () {
const start = Duration(seconds: 2);
const end = Duration(seconds: 3);
expect(
syncedLyricSegmentProgress(
position: const Duration(milliseconds: 1500),
start: start,
end: end,
),
0,
);
expect(
syncedLyricSegmentProgress(
position: const Duration(milliseconds: 2250),
start: start,
end: end,
),
0.25,
);
expect(
syncedLyricSegmentProgress(
position: const Duration(milliseconds: 3500),
start: start,
end: end,
),
1,
);
});
test('moves the reveal boundary from left to right', () {
expect(
syncedLyricsLeftToRightBoundary(left: 10, right: 110, progress: 0),
10,
);
expect(
syncedLyricsLeftToRightBoundary(left: 10, right: 110, progress: 0.5),
60,
);
expect(
syncedLyricsLeftToRightBoundary(left: 10, right: 110, progress: 1),
110,
);
});
});
}