// Copyright 2017 The Chromium 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_test/flutter_test.dart';
import 'package:flutter/widgets.dart';

void main() {
  testWidgets('AnimatedList initialItemCount', (WidgetTester tester) async {
    final Map<int, Animation<double>> animations = <int, Animation<double>>{};

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: AnimatedList(
          initialItemCount: 2,
          itemBuilder: (BuildContext context, int index, Animation<double> animation) {
            animations[index] = animation;
            return SizedBox(
              height: 100.0,
              child: Center(
                child: Text('item $index'),
              ),
            );
          },
        ),
      ),
    );

    expect(find.text('item 0'), findsOneWidget);
    expect(find.text('item 1'), findsOneWidget);
    expect(animations.containsKey(0), true);
    expect(animations.containsKey(1), true);
    expect(animations[0].value, 1.0);
    expect(animations[1].value, 1.0);
  });

  testWidgets('AnimatedList insert', (WidgetTester tester) async {
    final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: AnimatedList(
          key: listKey,
          itemBuilder: (BuildContext context, int index, Animation<double> animation) {
            return SizeTransition(
              key: ValueKey<int>(index),
              axis: Axis.vertical,
              sizeFactor: animation,
              child: SizedBox(
                height: 100.0,
                child: Center(
                  child: Text('item $index'),
                ),
              ),
            );
          },
        ),
      ),
    );

    double itemHeight(int index) => tester.getSize(find.byKey(ValueKey<int>(index), skipOffstage: false)).height;
    double itemTop(int index) => tester.getTopLeft(find.byKey(ValueKey<int>(index), skipOffstage: false)).dy;
    double itemBottom(int index) => tester.getBottomLeft(find.byKey(ValueKey<int>(index), skipOffstage: false)).dy;

    listKey.currentState.insertItem(0, duration: const Duration(milliseconds: 100));
    await tester.pump();

    // Newly inserted item 0's height should animate from 0 to 100
    expect(itemHeight(0), 0.0);
    await tester.pump(const Duration(milliseconds: 50));
    expect(itemHeight(0), 50.0);
    await tester.pump(const Duration(milliseconds: 50));
    expect(itemHeight(0), 100.0);

    // The list now contains one fully expanded item at the top:
    expect(find.text('item 0'), findsOneWidget);
    expect(itemTop(0), 0.0);
    expect(itemBottom(0), 100.0);

    listKey.currentState.insertItem(0, duration: const Duration(milliseconds: 100));
    listKey.currentState.insertItem(0, duration: const Duration(milliseconds: 100));
    await tester.pump();

    // The height of the newly inserted items at index 0 and 1 should animate from 0 to 100.
    // The height of the original item, now at index 2, should remain 100.
    expect(itemHeight(0), 0.0);
    expect(itemHeight(1), 0.0);
    expect(itemHeight(2), 100.0);
    await tester.pump(const Duration(milliseconds: 50));
    expect(itemHeight(0), 50.0);
    expect(itemHeight(1), 50.0);
    expect(itemHeight(2), 100.0);
    await tester.pump(const Duration(milliseconds: 50));
    expect(itemHeight(0), 100.0);
    expect(itemHeight(1), 100.0);
    expect(itemHeight(2), 100.0);

    // The newly inserted "item 1" and "item 2" appear above "item 0"
    expect(find.text('item 0'), findsOneWidget);
    expect(find.text('item 1'), findsOneWidget);
    expect(find.text('item 2'), findsOneWidget);
    expect(itemTop(0), 0.0);
    expect(itemBottom(0), 100.0);
    expect(itemTop(1), 100.0);
    expect(itemBottom(1), 200.0);
    expect(itemTop(2), 200.0);
    expect(itemBottom(2), 300.0);
  });

  testWidgets('AnimatedList remove', (WidgetTester tester) async {
    final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
    final List<int> items = <int>[0, 1, 2];

    Widget buildItem(BuildContext context, int item, Animation<double> animation) {
      return SizeTransition(
        key: ValueKey<int>(item),
        axis: Axis.vertical,
        sizeFactor: animation,
        child: SizedBox(
          height: 100.0,
          child: Center(
            child: Text('item $item', textDirection: TextDirection.ltr),
          ),
        ),
      );
    }

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: AnimatedList(
          key: listKey,
          initialItemCount: 3,
          itemBuilder: (BuildContext context, int index, Animation<double> animation) {
            return buildItem(context, items[index], animation);
          },
        ),
      ),
    );

    double itemTop(int index) => tester.getTopLeft(find.byKey(ValueKey<int>(index))).dy;
    double itemBottom(int index) => tester.getBottomLeft(find.byKey(ValueKey<int>(index))).dy;

    expect(find.text('item 0'), findsOneWidget);
    expect(find.text('item 1'), findsOneWidget);
    expect(find.text('item 2'), findsOneWidget);

    items.removeAt(0);
    listKey.currentState.removeItem(0,
      (BuildContext context, Animation<double> animation) => buildItem(context, 0, animation),
      duration: const Duration(milliseconds: 100),
    );

    // Items 0, 1, 2 at 0, 100, 200. All heights 100.
    expect(itemTop(0), 0.0);
    expect(itemBottom(0), 100.0);
    expect(itemTop(1), 100.0);
    expect(itemBottom(1), 200.0);
    expect(itemTop(2), 200.0);
    expect(itemBottom(2), 300.0);

    // Newly removed item 0's height should animate from 100 to 0 over 100ms

    // Items 0, 1, 2 at 0, 50, 150. Item 0's height is 50.
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 50));
    expect(itemTop(0), 0.0);
    expect(itemBottom(0), 50.0);
    expect(itemTop(1), 50.0);
    expect(itemBottom(1), 150.0);
    expect(itemTop(2), 150.0);
    expect(itemBottom(2), 250.0);

    // Items 1, 2 at 0, 100.
    await tester.pumpAndSettle();
    expect(itemTop(1), 0.0);
    expect(itemBottom(1), 100.0);
    expect(itemTop(2), 100.0);
    expect(itemBottom(2), 200.0);
  });
}