558 lines
15 KiB
Dart
558 lines
15 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:lottie/lottie.dart';
|
|
|
|
void main() {
|
|
tearDown(() {
|
|
Lottie.cache.clear();
|
|
});
|
|
|
|
testWidgets('Should settle if no animation', (tester) async {
|
|
var data = File('example/assets/HamburgerArrow.json').readAsBytesSync();
|
|
var composition = await LottieComposition.fromBytes(data);
|
|
|
|
await tester.pumpWidget(Lottie(composition: composition, animate: false));
|
|
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('onLoaded called with the correct composition', (tester) async {
|
|
late LottieComposition composition;
|
|
|
|
var file = SynchronousFile(File('example/assets/HamburgerArrow.json'));
|
|
|
|
await tester.pumpWidget(
|
|
LottieBuilder.file(
|
|
file,
|
|
onLoaded: (c) {
|
|
composition = c;
|
|
},
|
|
),
|
|
);
|
|
|
|
await tester.pump();
|
|
|
|
expect(composition.endFrame, 179.99);
|
|
});
|
|
|
|
testWidgets('onLoaded called when remplacing the widget animation', (
|
|
tester,
|
|
) async {
|
|
var hamburgerData = Future.value(
|
|
bytesForFile('example/assets/HamburgerArrow.json'),
|
|
);
|
|
var androidData = Future.value(
|
|
bytesForFile('example/assets/AndroidWave.json'),
|
|
);
|
|
|
|
var mockAsset = FakeAssetBundle({
|
|
'hamburger.json': hamburgerData,
|
|
'android.json': androidData,
|
|
});
|
|
|
|
var animation = AnimationController(vsync: tester);
|
|
|
|
LottieComposition? composition;
|
|
await tester.pumpWidget(
|
|
Lottie.asset(
|
|
'hamburger.json',
|
|
controller: animation,
|
|
bundle: mockAsset,
|
|
onLoaded: (c) {
|
|
composition = c;
|
|
},
|
|
),
|
|
);
|
|
await tester.pump();
|
|
expect(tester.takeException(), isNull);
|
|
//expect(find.byType(Lottie), findsOneWidget);
|
|
expect(composition, isNotNull);
|
|
expect(composition!.duration, const Duration(seconds: 6));
|
|
|
|
composition = null;
|
|
|
|
await tester.pumpWidget(
|
|
Lottie.asset(
|
|
'android.json',
|
|
controller: animation,
|
|
bundle: mockAsset,
|
|
onLoaded: (c) {
|
|
composition = c;
|
|
},
|
|
),
|
|
);
|
|
|
|
await tester.pump();
|
|
expect(composition, isNotNull);
|
|
expect(composition!.duration, const Duration(seconds: 2, milliseconds: 50));
|
|
});
|
|
|
|
testWidgets('onLoaded data race 1', (tester) async {
|
|
var hamburgerCompleter = Completer<ByteData>();
|
|
var androidCompleter = Completer<ByteData>();
|
|
|
|
var hamburgerData = bytesForFile('example/assets/HamburgerArrow.json');
|
|
var androidData = bytesForFile('example/assets/AndroidWave.json');
|
|
|
|
var mockAsset = FakeAssetBundle({
|
|
'hamburger.json': hamburgerCompleter.future,
|
|
'android.json': androidCompleter.future,
|
|
});
|
|
|
|
var animation = AnimationController(vsync: tester);
|
|
|
|
var onLoadedCount = 0;
|
|
|
|
LottieComposition? composition;
|
|
await tester.pumpWidget(
|
|
Lottie.asset(
|
|
'hamburger.json',
|
|
controller: animation,
|
|
bundle: mockAsset,
|
|
onLoaded: (c) {
|
|
composition = c;
|
|
++onLoadedCount;
|
|
},
|
|
),
|
|
);
|
|
await tester.pump();
|
|
expect(
|
|
find.byWidgetPredicate((w) => w is RawLottie && w.composition == null),
|
|
findsOneWidget,
|
|
);
|
|
expect(composition, isNull);
|
|
expect(onLoadedCount, 0);
|
|
|
|
await tester.pumpWidget(
|
|
Lottie.asset(
|
|
'android.json',
|
|
controller: animation,
|
|
bundle: mockAsset,
|
|
onLoaded: (c) {
|
|
composition = c;
|
|
++onLoadedCount;
|
|
},
|
|
),
|
|
);
|
|
|
|
await tester.pump();
|
|
expect(composition, isNull);
|
|
expect(onLoadedCount, 0);
|
|
|
|
hamburgerCompleter.complete(hamburgerData);
|
|
|
|
await tester.pump();
|
|
expect(composition, isNull);
|
|
expect(onLoadedCount, 0);
|
|
|
|
androidCompleter.complete(androidData);
|
|
|
|
await tester.pump();
|
|
expect(composition!.duration, const Duration(seconds: 2, milliseconds: 50));
|
|
expect(onLoadedCount, 1);
|
|
});
|
|
|
|
testWidgets('onLoaded data race 2', (tester) async {
|
|
var hamburgerCompleter = Completer<ByteData>();
|
|
var androidCompleter = Completer<ByteData>();
|
|
|
|
var hamburgerData = bytesForFile('example/assets/HamburgerArrow.json');
|
|
var androidData = bytesForFile('example/assets/AndroidWave.json');
|
|
|
|
var mockAsset = FakeAssetBundle({
|
|
'hamburger.json': hamburgerCompleter.future,
|
|
'android.json': androidCompleter.future,
|
|
});
|
|
|
|
var animation = AnimationController(vsync: tester);
|
|
|
|
var onLoadedCount = 0;
|
|
|
|
LottieComposition? composition;
|
|
await tester.pumpWidget(
|
|
Lottie.asset(
|
|
'hamburger.json',
|
|
controller: animation,
|
|
bundle: mockAsset,
|
|
onLoaded: (c) {
|
|
composition = c;
|
|
++onLoadedCount;
|
|
},
|
|
),
|
|
);
|
|
await tester.pump();
|
|
expect(
|
|
find.byWidgetPredicate((w) => w is RawLottie && w.composition == null),
|
|
findsOneWidget,
|
|
);
|
|
expect(composition, isNull);
|
|
expect(onLoadedCount, 0);
|
|
|
|
await tester.pumpWidget(
|
|
Lottie.asset(
|
|
'android.json',
|
|
controller: animation,
|
|
bundle: mockAsset,
|
|
onLoaded: (c) {
|
|
composition = c;
|
|
++onLoadedCount;
|
|
},
|
|
),
|
|
);
|
|
|
|
await tester.pump();
|
|
expect(composition, isNull);
|
|
expect(onLoadedCount, 0);
|
|
|
|
androidCompleter.complete(androidData);
|
|
|
|
await tester.pump();
|
|
expect(composition!.duration, const Duration(seconds: 2, milliseconds: 50));
|
|
expect(onLoadedCount, 1);
|
|
|
|
hamburgerCompleter.complete(hamburgerData);
|
|
|
|
await tester.pump();
|
|
expect(composition!.duration, const Duration(seconds: 2, milliseconds: 50));
|
|
expect(onLoadedCount, 1);
|
|
});
|
|
|
|
testWidgets('Should auto animate', (tester) async {
|
|
var composition = await LottieComposition.fromBytes(
|
|
File('example/assets/HamburgerArrow.json').readAsBytesSync(),
|
|
);
|
|
|
|
await tester.pumpWidget(Lottie(composition: composition));
|
|
|
|
await tester.pump();
|
|
|
|
var lottie = tester.firstWidget<AnimatedBuilder>(
|
|
find.byType(AnimatedBuilder),
|
|
);
|
|
expect(lottie.listenable, isNotNull);
|
|
expect(
|
|
(lottie.listenable as AnimationController).duration,
|
|
const Duration(seconds: 6),
|
|
);
|
|
expect((lottie.listenable as AnimationController).isAnimating, true);
|
|
|
|
await tester.pumpWidget(Lottie(composition: composition, animate: false));
|
|
|
|
lottie = tester.firstWidget<AnimatedBuilder>(find.byType(AnimatedBuilder));
|
|
expect(lottie.listenable, isNotNull);
|
|
expect(
|
|
(lottie.listenable as AnimationController).duration,
|
|
const Duration(seconds: 6),
|
|
);
|
|
expect((lottie.listenable as AnimationController).isAnimating, false);
|
|
|
|
await tester.pumpWidget(Lottie(composition: composition));
|
|
|
|
lottie = tester.firstWidget<AnimatedBuilder>(find.byType(AnimatedBuilder));
|
|
expect(lottie.listenable, isNotNull);
|
|
expect(
|
|
(lottie.listenable as AnimationController).duration,
|
|
const Duration(seconds: 6),
|
|
);
|
|
|
|
var animationController = AnimationController(
|
|
vsync: tester,
|
|
duration: const Duration(seconds: 2),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Lottie(composition: composition, controller: animationController.view),
|
|
);
|
|
|
|
lottie = tester.firstWidget<AnimatedBuilder>(find.byType(AnimatedBuilder));
|
|
expect(lottie.listenable, isNotNull);
|
|
expect(
|
|
(lottie.listenable as AnimationController).duration,
|
|
const Duration(seconds: 2),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Lottie(
|
|
composition: composition,
|
|
controller: animationController.view,
|
|
animate: false,
|
|
),
|
|
);
|
|
|
|
lottie = tester.firstWidget<AnimatedBuilder>(find.byType(AnimatedBuilder));
|
|
expect(lottie.listenable, isNotNull);
|
|
expect(
|
|
(lottie.listenable as AnimationController).duration,
|
|
const Duration(seconds: 2),
|
|
);
|
|
|
|
await tester.pumpWidget(Lottie(composition: composition, animate: false));
|
|
|
|
lottie = tester.firstWidget<AnimatedBuilder>(find.byType(AnimatedBuilder));
|
|
expect(lottie.listenable, isNotNull);
|
|
expect(
|
|
(lottie.listenable as AnimationController).duration,
|
|
const Duration(seconds: 6),
|
|
);
|
|
expect((lottie.listenable as AnimationController).isAnimating, false);
|
|
});
|
|
|
|
testWidgets('errorBuilder called when error', (tester) async {
|
|
var hamburgerData = Future.value(
|
|
bytesForFile('example/assets/HamburgerArrow.json'),
|
|
);
|
|
var mockAsset = FakeAssetBundle({'hamburger.json': hamburgerData});
|
|
|
|
var errorKey = UniqueKey();
|
|
var loadedCall = 0;
|
|
await tester.pumpWidget(
|
|
LottieBuilder.asset(
|
|
'error.json',
|
|
bundle: mockAsset,
|
|
errorBuilder: (c, e, stackTrace) => Container(key: errorKey),
|
|
onLoaded: (c) {
|
|
++loadedCall;
|
|
},
|
|
),
|
|
);
|
|
|
|
await tester.pump();
|
|
expect(find.byKey(errorKey), findsOneWidget);
|
|
expect(loadedCall, 0);
|
|
|
|
await tester.pumpWidget(
|
|
LottieBuilder.asset(
|
|
'hamburger.json',
|
|
bundle: mockAsset,
|
|
errorBuilder: (c, e, stackTrace) => Container(key: errorKey),
|
|
onLoaded: (c) {
|
|
++loadedCall;
|
|
},
|
|
),
|
|
);
|
|
await tester.pump();
|
|
|
|
expect(find.byType(Lottie), findsOneWidget);
|
|
expect(find.byKey(errorKey), findsNothing);
|
|
expect(loadedCall, 1);
|
|
});
|
|
|
|
testWidgets('Cache should be synchronous', (tester) async {
|
|
var hamburgerData = Future.value(
|
|
bytesForFile('example/assets/HamburgerArrow.json'),
|
|
);
|
|
var mockAsset = FakeAssetBundle({'hamburger.json': hamburgerData});
|
|
|
|
var loadedCall = 0;
|
|
var lottieWidget = LottieBuilder.asset(
|
|
'hamburger.json',
|
|
bundle: mockAsset,
|
|
onLoaded: (c) {
|
|
++loadedCall;
|
|
},
|
|
);
|
|
|
|
await tester.pumpWidget(lottieWidget);
|
|
expect(tester.widget<Lottie>(find.byType(Lottie)).composition, isNull);
|
|
await tester.pump();
|
|
expect(tester.widget<Lottie>(find.byType(Lottie)).composition, isNotNull);
|
|
|
|
await tester.pumpWidget(Column(children: [lottieWidget, lottieWidget]));
|
|
expect(
|
|
tester.widget<Lottie>(find.byType(Lottie).at(0)).composition,
|
|
isNotNull,
|
|
);
|
|
expect(
|
|
tester.widget<Lottie>(find.byType(Lottie).at(1)).composition,
|
|
isNotNull,
|
|
);
|
|
expect(loadedCall, 3);
|
|
});
|
|
|
|
testWidgets('Cache can be cleared', (tester) async {
|
|
var hamburgerData = Future.value(
|
|
bytesForFile('example/assets/HamburgerArrow.json'),
|
|
);
|
|
var mockAsset = FakeAssetBundle({'hamburger.json': hamburgerData});
|
|
|
|
var loadedCall = 0;
|
|
var lottieWidget = LottieBuilder.asset(
|
|
'hamburger.json',
|
|
bundle: mockAsset,
|
|
onLoaded: (c) {
|
|
++loadedCall;
|
|
},
|
|
);
|
|
|
|
await tester.pumpWidget(lottieWidget);
|
|
expect(tester.widget<Lottie>(find.byType(Lottie)).composition, isNull);
|
|
await tester.pump();
|
|
expect(tester.widget<Lottie>(find.byType(Lottie)).composition, isNotNull);
|
|
|
|
Lottie.cache.clear();
|
|
|
|
await tester.pumpWidget(Center(child: lottieWidget));
|
|
expect(tester.widget<Lottie>(find.byType(Lottie)).composition, isNull);
|
|
await tester.pump();
|
|
expect(tester.widget<Lottie>(find.byType(Lottie)).composition, isNotNull);
|
|
expect(loadedCall, 2);
|
|
});
|
|
|
|
testWidgets('onLoaded is ', (tester) async {
|
|
var hamburgerData = Future.value(
|
|
bytesForFile('example/assets/HamburgerArrow.json'),
|
|
);
|
|
var mockAsset = FakeAssetBundle({'hamburger.json': hamburgerData});
|
|
var provider = AssetLottie('hamburger.json', bundle: mockAsset);
|
|
|
|
await tester.pumpWidget(
|
|
KeyedSubtree(
|
|
key: UniqueKey(),
|
|
child: _LottieWithSetStateInOnLoaded(provider),
|
|
),
|
|
);
|
|
var state1 = tester.state<__LottieWithSetStateInOnLoadedState>(
|
|
find.byType(_LottieWithSetStateInOnLoaded),
|
|
);
|
|
expect(state1.loadedCount, 1);
|
|
await tester.pump();
|
|
expect(state1.loadedCount, 1);
|
|
|
|
await tester.pumpWidget(
|
|
KeyedSubtree(
|
|
key: UniqueKey(),
|
|
child: _LottieWithSetStateInOnLoaded(provider),
|
|
),
|
|
);
|
|
var state2 = tester.state<__LottieWithSetStateInOnLoadedState>(
|
|
find.byType(_LottieWithSetStateInOnLoaded),
|
|
);
|
|
expect(state2.loadedCount, 1);
|
|
await tester.pump();
|
|
expect(state2.loadedCount, 1);
|
|
expect(state1, isNot(state2));
|
|
});
|
|
|
|
testWidgets(
|
|
'if composition is static should create Lottie with [animate] false by default',
|
|
(tester) async {
|
|
await tester.pumpWidget(
|
|
LottieBuilder.memory(
|
|
File('test/data/static_lottie.json').readAsBytesSync(),
|
|
),
|
|
);
|
|
expect(tester.hasRunningAnimations, false);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'if composition is static and [animate] is true, should have animations',
|
|
(tester) async {
|
|
await tester.pumpWidget(
|
|
LottieBuilder.memory(
|
|
File('test/data/static_lottie.json').readAsBytesSync(),
|
|
animate: true,
|
|
),
|
|
);
|
|
expect(tester.hasRunningAnimations, true);
|
|
},
|
|
);
|
|
|
|
testWidgets('AssetLottie uses DefaultAssetBundle', (tester) async {
|
|
var hamburgerData = Future.value(
|
|
bytesForFile('example/assets/HamburgerArrow.json'),
|
|
);
|
|
var mockAsset = FakeAssetBundle({
|
|
'hamburger.json': hamburgerData,
|
|
'other.json': hamburgerData,
|
|
});
|
|
await tester.pumpWidget(
|
|
DefaultAssetBundle(
|
|
bundle: mockAsset,
|
|
child: Lottie.asset('hamburger.json'),
|
|
),
|
|
);
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(find.byType(RawLottie), findsOneWidget);
|
|
expect(
|
|
find.byWidgetPredicate((w) => w is RawLottie && w.composition != null),
|
|
findsOneWidget,
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
DefaultAssetBundle(bundle: mockAsset, child: Lottie.asset('other.json')),
|
|
);
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(
|
|
find.byWidgetPredicate((w) => w is RawLottie && w.composition != null),
|
|
findsOneWidget,
|
|
);
|
|
});
|
|
|
|
testWidgets('expected an int', (tester) async {
|
|
var data = File(
|
|
'example/assets/Tests/kona_splash_animation.json',
|
|
).readAsBytesSync();
|
|
var composition = await LottieComposition.fromBytes(data);
|
|
|
|
await tester.pumpWidget(Lottie(composition: composition, animate: false));
|
|
|
|
await tester.pumpAndSettle();
|
|
});
|
|
}
|
|
|
|
class SynchronousFile extends Fake implements File {
|
|
final File _real;
|
|
|
|
SynchronousFile(this._real);
|
|
|
|
@override
|
|
String get path => _real.path;
|
|
|
|
@override
|
|
Future<Uint8List> readAsBytes() => Future.value(_real.readAsBytesSync());
|
|
}
|
|
|
|
ByteData bytesForFile(String path) =>
|
|
File(path).readAsBytesSync().buffer.asByteData();
|
|
|
|
class FakeAssetBundle extends Fake implements AssetBundle {
|
|
final Map<String, Future<ByteData>> data;
|
|
|
|
FakeAssetBundle(this.data);
|
|
|
|
@override
|
|
Future<ByteData> load(String key) {
|
|
return data[key] ?? (Future.error('Asset $key not found'));
|
|
}
|
|
}
|
|
|
|
class _LottieWithSetStateInOnLoaded extends StatefulWidget {
|
|
final LottieProvider lottie;
|
|
|
|
const _LottieWithSetStateInOnLoaded(this.lottie);
|
|
|
|
@override
|
|
State<_LottieWithSetStateInOnLoaded> createState() =>
|
|
__LottieWithSetStateInOnLoadedState();
|
|
}
|
|
|
|
class __LottieWithSetStateInOnLoadedState
|
|
extends State<_LottieWithSetStateInOnLoaded> {
|
|
var loadedCount = 0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return LottieBuilder(
|
|
lottie: widget.lottie,
|
|
onLoaded: (_) {
|
|
setState(() {
|
|
++loadedCount;
|
|
});
|
|
},
|
|
);
|
|
}
|
|
}
|