// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('runs animations', (WidgetTester tester) async {
    final AnimationController controller = AnimationController(
      vsync: const TestVSync(),
      duration: const Duration(milliseconds: 300),
    );

    await tester.pumpWidget(Center(
      child: DualTransitionBuilder(
        animation: controller,
        forwardBuilder: (
          BuildContext context,
          Animation<double> animation,
          Widget? child,
        ) {
          return ScaleTransition(
            scale: animation,
            child: child,
          );
        },
        reverseBuilder: (
          BuildContext context,
          Animation<double> animation,
          Widget? child,
        ) {
          return FadeTransition(
            opacity: Tween<double>(begin: 1.0, end: 0.0).animate(animation),
            child: child,
          );
        },
        child: Container(
          color: Colors.green,
          height: 100,
          width: 100,
        ),
      ),
    ));
    expect(_getScale(tester), 0.0);
    expect(_getOpacity(tester), 1.0);

    controller.forward();
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 150));
    expect(_getScale(tester), 0.5);
    expect(_getOpacity(tester), 1.0);

    await tester.pump(const Duration(milliseconds: 150));
    expect(_getScale(tester), 1.0);
    expect(_getOpacity(tester), 1.0);

    await tester.pumpAndSettle();
    expect(_getScale(tester), 1.0);
    expect(_getOpacity(tester), 1.0);

    controller.reverse();
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 150));
    expect(_getScale(tester), 1.0);
    expect(_getOpacity(tester), 0.5);

    await tester.pump(const Duration(milliseconds: 150));
    expect(_getScale(tester), 1.0);
    expect(_getOpacity(tester), 0.0);

    await tester.pumpAndSettle();
    expect(_getScale(tester), 0.0);
    expect(_getOpacity(tester), 1.0);
  });

  testWidgets('keeps state', (WidgetTester tester) async {
    final AnimationController controller = AnimationController(
      vsync: const TestVSync(),
      duration: const Duration(milliseconds: 300),
    );

    await tester.pumpWidget(Directionality(
      textDirection: TextDirection.ltr,
      child: Center(
        child: DualTransitionBuilder(
          animation: controller,
          forwardBuilder: (
            BuildContext context,
            Animation<double> animation,
            Widget? child,
          ) {
            return ScaleTransition(
              scale: animation,
              child: child,
            );
          },
          reverseBuilder: (
            BuildContext context,
            Animation<double> animation,
            Widget? child,
          ) {
            return FadeTransition(
              opacity: Tween<double>(begin: 1.0, end: 0.0).animate(animation),
              child: child,
            );
          },
          child: const _StatefulTestWidget(name: 'Foo'),
        ),
      ),
    ));
    final State<StatefulWidget> state =
        tester.state(find.byType(_StatefulTestWidget));
    expect(state, isNotNull);

    controller.forward();
    await tester.pump();
    expect(state, same(tester.state(find.byType(_StatefulTestWidget))));
    await tester.pump(const Duration(milliseconds: 150));
    expect(state, same(tester.state(find.byType(_StatefulTestWidget))));

    await tester.pump(const Duration(milliseconds: 150));
    expect(state, same(tester.state(find.byType(_StatefulTestWidget))));

    await tester.pumpAndSettle();
    expect(state, same(tester.state(find.byType(_StatefulTestWidget))));

    controller.reverse();
    await tester.pump();
    expect(state, same(tester.state(find.byType(_StatefulTestWidget))));
    await tester.pump(const Duration(milliseconds: 150));
    expect(state, same(tester.state(find.byType(_StatefulTestWidget))));

    await tester.pump(const Duration(milliseconds: 150));
    expect(state, same(tester.state(find.byType(_StatefulTestWidget))));

    await tester.pumpAndSettle();
    expect(state, same(tester.state(find.byType(_StatefulTestWidget))));
  });

  testWidgets('does not jump when interrupted - forward', (WidgetTester tester) async {
    final AnimationController controller = AnimationController(
      vsync: const TestVSync(),
      duration: const Duration(milliseconds: 300),
    );
    await tester.pumpWidget(Center(
      child: DualTransitionBuilder(
        animation: controller,
        forwardBuilder: (
          BuildContext context,
          Animation<double> animation,
          Widget? child,
        ) {
          return ScaleTransition(
            scale: animation,
            child: child,
          );
        },
        reverseBuilder: (
          BuildContext context,
          Animation<double> animation,
          Widget? child,
        ) {
          return FadeTransition(
            opacity: Tween<double>(begin: 1.0, end: 0.0).animate(animation),
            child: child,
          );
        },
        child: Container(
          color: Colors.green,
          height: 100,
          width: 100,
        ),
      ),
    ));
    expect(_getScale(tester), 0.0);
    expect(_getOpacity(tester), 1.0);

    controller.forward();
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 150));
    expect(_getScale(tester), 0.5);
    expect(_getOpacity(tester), 1.0);

    controller.reverse();
    expect(_getScale(tester), 0.5);
    expect(_getOpacity(tester), 1.0);
    await tester.pump();
    expect(_getScale(tester), 0.5);
    expect(_getOpacity(tester), 1.0);

    await tester.pump(const Duration(milliseconds: 75));
    expect(_getScale(tester), 0.25);
    expect(_getOpacity(tester), 1.0);

    await tester.pump(const Duration(milliseconds: 75));
    expect(_getScale(tester), 0.0);
    expect(_getOpacity(tester), 1.0);

    await tester.pumpAndSettle();
    expect(_getScale(tester), 0.0);
    expect(_getOpacity(tester), 1.0);
  });

  testWidgets('does not jump when interrupted - reverse', (WidgetTester tester) async {
    final AnimationController controller = AnimationController(
      value: 1.0,
      vsync: const TestVSync(),
      duration: const Duration(milliseconds: 300),
    );
    await tester.pumpWidget(Center(
      child: DualTransitionBuilder(
        animation: controller,
        forwardBuilder: (
          BuildContext context,
          Animation<double> animation,
          Widget? child,
        ) {
          return ScaleTransition(
            scale: animation,
            child: child,
          );
        },
        reverseBuilder: (
          BuildContext context,
          Animation<double> animation,
          Widget? child,
        ) {
          return FadeTransition(
            opacity: Tween<double>(begin: 1.0, end: 0.0).animate(animation),
            child: child,
          );
        },
        child: Container(
          color: Colors.green,
          height: 100,
          width: 100,
        ),
      ),
    ));
    expect(_getScale(tester), 1.0);
    expect(_getOpacity(tester), 1.0);

    controller.reverse();
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 150));
    expect(_getScale(tester), 1.0);
    expect(_getOpacity(tester), 0.5);

    controller.forward();
    expect(_getScale(tester), 1.0);
    expect(_getOpacity(tester), 0.5);
    await tester.pump();
    expect(_getScale(tester), 1.0);
    expect(_getOpacity(tester), 0.5);

    await tester.pump(const Duration(milliseconds: 75));
    expect(_getScale(tester), 1.0);
    expect(_getOpacity(tester), 0.75);

    await tester.pump(const Duration(milliseconds: 75));
    expect(_getScale(tester), 1.0);
    expect(_getOpacity(tester), 1.0);

    await tester.pumpAndSettle();
    expect(_getScale(tester), 1.0);
    expect(_getOpacity(tester), 1.0);
  });
}

double _getScale(WidgetTester tester) {
  final ScaleTransition scale = tester.widget(find.byType(ScaleTransition));
  return scale.scale.value;
}

double _getOpacity(WidgetTester tester) {
  final FadeTransition scale = tester.widget(find.byType(FadeTransition));
  return scale.opacity.value;
}

class _StatefulTestWidget extends StatefulWidget {
  const _StatefulTestWidget({required this.name});

  final String name;

  @override
  State<_StatefulTestWidget> createState() => _StatefulTestWidgetState();
}

class _StatefulTestWidgetState extends State<_StatefulTestWidget> {
  @override
  Widget build(BuildContext context) {
    return Text(widget.name);
  }
}
