/* * Copyright (c) 2026 Malloc LLC (malloc.io) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:splash_video/splash_video.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('SplashVideoPage Configuration Validation', () { testWidgets('throws error when using both nextScreen and onVideoComplete', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SplashVideo( source: AssetSource('assets/videos/splash.mp4'), nextScreen: const Scaffold(), onVideoComplete: () {}, ), ), ); // Should throw ArgumentError during build expect(tester.takeException(), isA()); }); testWidgets('throws error when using nextScreen with looping video', (WidgetTester tester) async { final controller = SplashVideoController(loopVideo: true); await tester.pumpWidget( MaterialApp( home: SplashVideo( source: AssetSource('assets/videos/splash.mp4'), controller: controller, nextScreen: const Scaffold(), ), ), ); expect(tester.takeException(), isA()); controller.dispose(); }); testWidgets('throws error when using onVideoComplete with looping video', (WidgetTester tester) async { final controller = SplashVideoController(loopVideo: true); await tester.pumpWidget( MaterialApp( home: SplashVideo( source: AssetSource('assets/videos/splash.mp4'), controller: controller, onVideoComplete: () {}, ), ), ); expect(tester.takeException(), isA()); controller.dispose(); }); }); group('SplashVideoPage Error Handling', () { testWidgets('shows default error UI when video source is invalid', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SplashVideo( source: AssetSource('assets/invalid/nonexistent.mp4'), nextScreen: const Scaffold(), ), ), ); // Wait for error to be detected await tester.pumpAndSettle(const Duration(seconds: 5)); // Should show error icon and message expect(find.byIcon(Icons.error_outline), findsOneWidget); expect(find.text('Video Load Error'), findsOneWidget); }); testWidgets('calls onVideoError callback when provided', (WidgetTester tester) async { String? capturedError; await tester.pumpWidget( MaterialApp( home: SplashVideo( source: AssetSource('assets/invalid/nonexistent.mp4'), onVideoError: (error) { capturedError = error; }, ), ), ); // Wait for error to be detected await tester.pumpAndSettle(const Duration(seconds: 5)); expect(capturedError, isNotNull); expect(capturedError, contains('Failed')); }); testWidgets('handles network URL errors gracefully', (WidgetTester tester) async { String? error; await tester.pumpWidget( MaterialApp( home: SplashVideo( source: NetworkFileSource('https://invalid.example.com/video.mp4'), onVideoError: (e) => error = e, ), ), ); await tester.pumpAndSettle(const Duration(seconds: 5)); // Should capture network error expect(error, isNotNull); }); }); group('SplashVideoPage Overlays', () { testWidgets('renders title widget overlay', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SplashVideo( source: AssetSource('assets/videos/splash.mp4'), titleWidget: const Text('Welcome'), onVideoError: (_) {}, ), ), ); await tester.pump(); expect(find.text('Welcome'), findsOneWidget); }); testWidgets('renders footer widget overlay', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SplashVideo( source: AssetSource('assets/videos/splash.mp4'), footerWidget: const Text('Loading...'), onVideoError: (_) {}, ), ), ); await tester.pump(); expect(find.text('Loading...'), findsOneWidget); }); testWidgets('renders custom overlay from builder', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SplashVideo( source: AssetSource('assets/videos/splash.mp4'), overlayBuilder: (context) => const Text('Custom Overlay'), onVideoError: (_) {}, ), ), ); await tester.pump(); expect(find.text('Custom Overlay'), findsOneWidget); }); testWidgets('renders all overlays together', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SplashVideo( source: AssetSource('assets/videos/splash.mp4'), titleWidget: const Text('Title'), footerWidget: const Text('Footer'), overlayBuilder: (context) => const Text('Custom'), onVideoError: (_) {}, ), ), ); await tester.pump(); expect(find.text('Title'), findsOneWidget); expect(find.text('Footer'), findsOneWidget); expect(find.text('Custom'), findsOneWidget); }); }); group('SplashVideoController Integration', () { testWidgets('controller can skip video', (WidgetTester tester) async { final controller = SplashVideoController(loopVideo: true); var completeCalled = false; await tester.pumpWidget( MaterialApp( home: Builder( builder: (context) => SplashVideo( source: AssetSource('assets/videos/splash.mp4'), controller: controller, footerWidget: ElevatedButton( onPressed: () { controller.skip(); completeCalled = true; }, child: const Text('Skip'), ), onVideoError: (_) {}, ), ), ), ); await tester.pump(); // Tap skip button await tester.tap(find.text('Skip')); await tester.pump(); expect(completeCalled, isTrue); expect(controller.skipRequested, isTrue); controller.dispose(); }); testWidgets('controller provides player access after initialization', (WidgetTester tester) async { final controller = SplashVideoController(); await tester.pumpWidget( MaterialApp( home: SplashVideo( source: AssetSource('assets/videos/splash.mp4'), controller: controller, onVideoError: (_) {}, ), ), ); await tester.pumpAndSettle(); // After initialization, player should be accessible // Note: This may throw if video fails to load try { final player = controller.player; expect(player, isNotNull); } catch (e) { // Expected if video doesn't load in test environment expect(e, isA()); } controller.dispose(); }); }); group('Source Types Integration', () { testWidgets('AssetSource works with valid asset', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SplashVideo( source: AssetSource('assets/videos/splash.mp4'), onVideoError: (_) {}, ), ), ); await tester.pump(); // Widget should be created without exceptions expect(find.byType(SplashVideo), findsOneWidget); }); testWidgets('NetworkFileSource accepts valid URL', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SplashVideo( source: NetworkFileSource('https://example.com/video.mp4'), onVideoError: (_) {}, ), ), ); await tester.pump(); expect(find.byType(SplashVideo), findsOneWidget); }); testWidgets('DeviceFileSource accepts file path', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SplashVideo( source: DeviceFileSource('/path/to/video.mp4'), onVideoError: (_) {}, ), ), ); await tester.pump(); expect(find.byType(SplashVideo), findsOneWidget); }); }); group('VideoConfig Integration', () { testWidgets('respects useSafeArea configuration', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SplashVideo( source: AssetSource('assets/videos/splash.mp4'), videoConfig: const VideoConfig(useSafeArea: true), onVideoError: (_) {}, ), ), ); await tester.pump(); expect(find.byType(SplashVideo), findsOneWidget); }); testWidgets('supports different visibility modes', (WidgetTester tester) async { for (final mode in VideoScaleMode.values) { await tester.pumpWidget( MaterialApp( home: SplashVideo( source: AssetSource('assets/videos/splash.mp4'), videoConfig: VideoConfig(scale: mode), onVideoError: (_) {}, ), ), ); await tester.pump(); expect(find.byType(SplashVideo), findsOneWidget); // Clean up for next iteration await tester.pumpWidget(Container()); } }); }); }