Skip to content

Commit

Permalink
lightbox test: Add video player regression tests
Browse files Browse the repository at this point in the history
  • Loading branch information
rajveermalviya committed May 24, 2024
1 parent e453648 commit 75f365e
Showing 1 changed file with 242 additions and 27 deletions.
269 changes: 242 additions & 27 deletions test/widgets/lightbox_test.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'dart:async';
import 'dart:math' as math;

import 'package:checks/checks.dart';
import 'package:clock/clock.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
Expand All @@ -16,11 +19,29 @@ import '../model/binding.dart';
class FakeVideoPlayerPlatform extends Fake
with MockPlatformInterfaceMixin
implements VideoPlayerPlatform {
static const int _textureId = 0xffffffff;
static const String kTestVideoUrl = "https://a/video.mp4";
static const String kTestUnsupportedVideoUrl = "https://a/unsupported.mp4";
static const Duration kTestVideoDuration = Duration(seconds: 10);

static const int _kTextureId = 0xffffffff;

static final List<String> _callLogs = [];
static StreamController<VideoEvent> _streamController = StreamController<VideoEvent>();
static bool initialized = false;
static bool isPlaying = false;
static bool _initialized = false;
static bool _hasError = false;
static Duration _position = Duration.zero;
static Duration _lastSetPosition = Duration.zero;
static Stopwatch? _stopwatch;

static List<String> get callLogs => _callLogs;
static bool get initialized => _initialized;
static bool get hasError => _hasError;
static bool get isPlaying => _stopwatch?.isRunning ?? false;

static Duration get position {
_updatePosition();
return _position;
}

static void registerWith() {
VideoPlayerPlatform.instance = FakeVideoPlayerPlatform();
Expand All @@ -29,49 +50,89 @@ class FakeVideoPlayerPlatform extends Fake
static void reset() {
_streamController.close();
_streamController = StreamController<VideoEvent>();
initialized = false;
isPlaying = false;
_callLogs.clear();
_initialized = false;
_position = Duration.zero;
_lastSetPosition = Duration.zero;
_stopwatch?.stop();
_stopwatch?.reset();
}

static void _pause() {
_stopwatch?.stop();
_streamController.add(VideoEvent(
eventType: VideoEventType.isPlayingStateUpdate,
isPlaying: false,
));
}

static void _updatePosition() {
assert(_stopwatch != null);
_position = _stopwatch!.elapsed + _lastSetPosition;
if (kTestVideoDuration.compareTo(_position) <= 0) {
_position = kTestVideoDuration;
_pause();
}
}

@override
Future<void> init() async {}
Future<void> init() async {
_callLogs.add('init');
}

@override
Future<void> dispose(int textureId) async {
_callLogs.add('dispose');
if (hasError) {
assert(!initialized);
assert(textureId == VideoPlayerController.kUninitializedTextureId);
return;
}

assert(initialized);
assert(textureId == _textureId);
initialized = false;
assert(textureId == _kTextureId);
}

@override
Future<int?> create(DataSource dataSource) async {
_callLogs.add('create');
assert(!initialized);
initialized = true;
if (dataSource.uri == kTestUnsupportedVideoUrl) {
_hasError = true;
_streamController.addError(PlatformException(code: "VideoError", message: "Failed to load video: Cannot Open"));
return null;
}

_stopwatch = clock.stopwatch();
_initialized = true;
_streamController.add(VideoEvent(
eventType: VideoEventType.initialized,
duration: const Duration(seconds: 1),
duration: kTestVideoDuration,
size: const Size(0, 0),
rotationCorrection: 0,
));
return _textureId;
return _kTextureId;
}

@override
Stream<VideoEvent> videoEventsFor(int textureId) {
assert(textureId == _textureId);
_callLogs.add('videoEventsFor');
assert(textureId == _kTextureId);
return _streamController.stream;
}

@override
Future<void> setLooping(int textureId, bool looping) async {
assert(textureId == _textureId);
_callLogs.add('setLooping');
assert(textureId == _kTextureId);
assert(!looping);
}

@override
Future<void> play(int textureId) async {
assert(textureId == _textureId);
isPlaying = true;
_callLogs.add('play');
assert(textureId == _kTextureId);
_stopwatch?.start();
_streamController.add(VideoEvent(
eventType: VideoEventType.isPlayingStateUpdate,
isPlaying: true,
Expand All @@ -80,27 +141,44 @@ class FakeVideoPlayerPlatform extends Fake

@override
Future<void> pause(int textureId) async {
assert(textureId == _textureId);
isPlaying = false;
_streamController.add(VideoEvent(
eventType: VideoEventType.isPlayingStateUpdate,
isPlaying: false,
));
_callLogs.add('pause');
assert(textureId == _kTextureId);
_pause();
}

@override
Future<void> setVolume(int textureId, double volume) async {
assert(textureId == _textureId);
_callLogs.add('setVolume');
assert(textureId == _kTextureId);
}

@override
Future<void> seekTo(int textureId, Duration pos) async {
_callLogs.add('seekTo');
assert(textureId == _kTextureId);

_lastSetPosition = Duration(
microseconds: math.min(pos.inMicroseconds, kTestVideoDuration.inMicroseconds));
_stopwatch?.reset();
}

@override
Future<void> setPlaybackSpeed(int textureId, double speed) async {
assert(textureId == _textureId);
_callLogs.add('setPlaybackSpeed');
assert(textureId == _kTextureId);
}

@override
Future<Duration> getPosition(int textureId) async {
_callLogs.add('getPosition');
assert(textureId == _kTextureId);
return position;
}

@override
Widget buildView(int textureId) {
assert(textureId == _textureId);
_callLogs.add('buildView');
assert(textureId == _kTextureId);
return const SizedBox(width: 100, height: 100);
}
}
Expand All @@ -109,6 +187,16 @@ void main() {
TestZulipBinding.ensureInitialized();

group("VideoLightboxPage", () {
void verifySliderPosition(WidgetTester tester, Duration duration) {
final slider = tester.widget(find.byType(Slider)) as Slider;
check(slider.value)
.equals(duration.inMilliseconds.toDouble());
final positionLabel = tester.widget(
find.byKey(VideoPositionSliderControl.kCurrentPositionLabelKey)) as VideoDurationLabel;
check(positionLabel.duration)
.equals(duration);
}

FakeVideoPlayerPlatform.registerWith();

Future<void> setupPage(WidgetTester tester, {
Expand All @@ -127,11 +215,13 @@ void main() {
routeEntranceAnimation: kAlwaysCompleteAnimation,
message: eg.streamMessage(),
src: videoSrc)))));
await tester.pumpAndSettle();
await tester.pump(); // global store
await tester.pump(); // per-account store
await tester.pump(); // video controller initialization
}

testWidgets('shows a VideoPlayer, and video is playing', (tester) async {
await setupPage(tester, videoSrc: Uri.parse('https://a/b.mp4'));
await setupPage(tester, videoSrc: Uri.parse(FakeVideoPlayerPlatform.kTestVideoUrl));

check(FakeVideoPlayerPlatform.initialized).isTrue();
check(FakeVideoPlayerPlatform.isPlaying).isTrue();
Expand All @@ -140,7 +230,7 @@ void main() {
});

testWidgets('toggles between play and pause', (tester) async {
await setupPage(tester, videoSrc: Uri.parse('https://a/b.mp4'));
await setupPage(tester, videoSrc: Uri.parse(FakeVideoPlayerPlatform.kTestVideoUrl));
check(FakeVideoPlayerPlatform.isPlaying).isTrue();

await tester.tap(find.byIcon(Icons.pause_circle_rounded));
Expand All @@ -152,5 +242,130 @@ void main() {
await tester.tap(find.byIcon(Icons.play_circle_rounded));
check(FakeVideoPlayerPlatform.isPlaying).isTrue();
});

testWidgets('unsupported video shows an error dialog', (tester) async {
await setupPage(tester, videoSrc: Uri.parse(FakeVideoPlayerPlatform.kTestUnsupportedVideoUrl));
await tester.ensureVisible(find.text("Unable to play the video"));
});

testWidgets('video advances overtime & stops playing when it ends', (tester) async {
await setupPage(tester, videoSrc: Uri.parse(FakeVideoPlayerPlatform.kTestVideoUrl));
check(FakeVideoPlayerPlatform.isPlaying).isTrue();
check(FakeVideoPlayerPlatform.position).equals(Duration.zero);

final halfTime = FakeVideoPlayerPlatform.kTestVideoDuration * 0.5;

await tester.pump(halfTime);
verifySliderPosition(tester, halfTime);
check(FakeVideoPlayerPlatform.position).equals(halfTime);
check(FakeVideoPlayerPlatform.isPlaying).isTrue();

// Near the end of the video.
await tester.pump(halfTime - const Duration(milliseconds: 500));
verifySliderPosition(
tester, FakeVideoPlayerPlatform.kTestVideoDuration - const Duration(milliseconds: 500));
check(FakeVideoPlayerPlatform.position)
.equals(FakeVideoPlayerPlatform.kTestVideoDuration - const Duration(milliseconds: 500));
check(FakeVideoPlayerPlatform.isPlaying).isTrue();

// At exactly the end of the video.
await tester.pump(const Duration(milliseconds: 500));
verifySliderPosition(tester, FakeVideoPlayerPlatform.kTestVideoDuration);
check(FakeVideoPlayerPlatform.position).equals(FakeVideoPlayerPlatform.kTestVideoDuration);
check(FakeVideoPlayerPlatform.isPlaying).isFalse();

// After the video ended.
await tester.pump(const Duration(milliseconds: 500));
verifySliderPosition(tester, FakeVideoPlayerPlatform.kTestVideoDuration);
check(FakeVideoPlayerPlatform.position).equals(FakeVideoPlayerPlatform.kTestVideoDuration);
check(FakeVideoPlayerPlatform.isPlaying).isFalse();
});

testWidgets('ensure \'seekTo\' is called only once', (tester) async {
await setupPage(tester, videoSrc: Uri.parse(FakeVideoPlayerPlatform.kTestVideoUrl));
check(FakeVideoPlayerPlatform.isPlaying).isTrue();
check(FakeVideoPlayerPlatform.position).equals(Duration.zero);

const padding = 24.0;
final rect = tester.getRect(find.byType(Slider));
final trackWidth = rect.width - padding - padding;
final trackStartPos = rect.centerLeft + const Offset(padding, 0);
final twentyPercent = trackWidth * 0.2; // 20% increments

// Verify the actually displayed current position at each
// gesture increments.
final gesture = await tester.startGesture(trackStartPos);
await tester.pump();
verifySliderPosition(tester, Duration.zero);
check(FakeVideoPlayerPlatform.position).equals(Duration.zero);

await gesture.moveBy(Offset(twentyPercent, 0.0));
await tester.pump();
verifySliderPosition(tester, FakeVideoPlayerPlatform.kTestVideoDuration * 0.2);
check(FakeVideoPlayerPlatform.position).equals(Duration.zero);

await gesture.moveBy(Offset(twentyPercent, 0.0));
await tester.pump();
verifySliderPosition(tester, FakeVideoPlayerPlatform.kTestVideoDuration * 0.4);
check(FakeVideoPlayerPlatform.position).equals(Duration.zero);

await gesture.moveBy(Offset(twentyPercent, 0.0));
await tester.pump();
verifySliderPosition(tester, FakeVideoPlayerPlatform.kTestVideoDuration * 0.6);
check(FakeVideoPlayerPlatform.position).equals(Duration.zero);

await gesture.up();
await tester.pump();
verifySliderPosition(tester, FakeVideoPlayerPlatform.kTestVideoDuration * 0.6);
check(FakeVideoPlayerPlatform.position).equals(FakeVideoPlayerPlatform.kTestVideoDuration * 0.6);

// Verify seekTo is called only once.
check(FakeVideoPlayerPlatform.callLogs.where((v) => v == 'seekTo').length).equals(1);
});

testWidgets('video advances overtime after dragging the slider', (tester) async {
await setupPage(tester, videoSrc: Uri.parse(FakeVideoPlayerPlatform.kTestVideoUrl));
check(FakeVideoPlayerPlatform.isPlaying).isTrue();
check(FakeVideoPlayerPlatform.position).equals(Duration.zero);

const padding = 24.0;
final rect = tester.getRect(find.byType(Slider));
final trackWidth = rect.width - padding - padding;
final trackStartPos = rect.centerLeft + const Offset(padding, 0);
final fiftyPercent = trackWidth * 0.5;
final halfTime = FakeVideoPlayerPlatform.kTestVideoDuration * 0.5;

final gesture = await tester.startGesture(trackStartPos);
await tester.pump();
verifySliderPosition(tester, Duration.zero);
check(FakeVideoPlayerPlatform.position).equals(Duration.zero);

await gesture.moveBy(Offset(fiftyPercent, 0));
await tester.pump();
verifySliderPosition(tester, halfTime);
check(FakeVideoPlayerPlatform.position).equals(Duration.zero);

await gesture.up();
await tester.pump();
verifySliderPosition(tester, halfTime);

// Verify that after dragging ends, video position is at the
// halfway point, and after that it starts advancing as the time
// passes.
check(FakeVideoPlayerPlatform.position).equals(halfTime);

const waitTime = Duration(seconds: 1);
await tester.pump(waitTime);
verifySliderPosition(tester, halfTime + (waitTime * 1));
check(FakeVideoPlayerPlatform.position).equals(halfTime + (waitTime * 1));

await tester.pump(waitTime);
verifySliderPosition(tester, halfTime + (waitTime * 2));
check(FakeVideoPlayerPlatform.position).equals(halfTime + (waitTime * 2));

await tester.pump(waitTime);
verifySliderPosition(tester, halfTime + (waitTime * 3));
check(FakeVideoPlayerPlatform.position).equals(halfTime + (waitTime * 3));
});
});
}

0 comments on commit 75f365e

Please sign in to comment.