// 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_test/flutter_test.dart';
import 'package:flutter/material.dart';

void main() {
  testWidgets('ListView can handle shrinking top elements', (WidgetTester tester) async {
    final ScrollController controller = ScrollController();
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: ListView(
          cacheExtent: 0.0,
          controller: controller,
          children: const <Widget>[
            SizedBox(height: 400.0, child: Text('1')),
            SizedBox(height: 400.0, child: Text('2')),
            SizedBox(height: 400.0, child: Text('3')),
            SizedBox(height: 400.0, child: Text('4')),
            SizedBox(height: 400.0, child: Text('5')),
            SizedBox(height: 400.0, child: Text('6')),
          ],
        ),
      ),
    );

    controller.jumpTo(1000.0);
    await tester.pump();

    expect(tester.getTopLeft(find.text('4')).dy, equals(200.0));

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: ListView(
          cacheExtent: 0.0,
          controller: controller,
          children: const <Widget>[
            SizedBox(height: 200.0, child: Text('1')),
            SizedBox(height: 400.0, child: Text('2')),
            SizedBox(height: 400.0, child: Text('3')),
            SizedBox(height: 400.0, child: Text('4')),
            SizedBox(height: 400.0, child: Text('5')),
            SizedBox(height: 400.0, child: Text('6')),
          ],
        ),
      ),
    );

    expect(controller.offset, equals(1000.0));
    expect(tester.getTopLeft(find.text('4')).dy, equals(200.0));

    controller.jumpTo(300.0);
    await tester.pump();

    expect(controller.offset, equals(300.0));
    expect(tester.getTopLeft(find.text('2')).dy, equals(100.0));

    controller.jumpTo(50.0);
    await tester.pump();

    expect(controller.offset, equals(0.0));
    expect(tester.getTopLeft(find.text('2')).dy, equals(200.0));
  });

  testWidgets('ListView can handle shrinking top elements with cache extent', (WidgetTester tester) async {
    final ScrollController controller = ScrollController();
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: ListView(
          controller: controller,
          children: const <Widget>[
            SizedBox(height: 400.0, child: Text('1')),
            SizedBox(height: 400.0, child: Text('2')),
            SizedBox(height: 400.0, child: Text('3')),
            SizedBox(height: 400.0, child: Text('4')),
            SizedBox(height: 400.0, child: Text('5')),
            SizedBox(height: 400.0, child: Text('6')),
          ],
        ),
      ),
    );

    controller.jumpTo(1000.0);
    await tester.pump();

    expect(tester.getTopLeft(find.text('4')).dy, equals(200.0));

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: ListView(
          controller: controller,
          children: const <Widget>[
            SizedBox(height: 200.0, child: Text('1')),
            SizedBox(height: 400.0, child: Text('2')),
            SizedBox(height: 400.0, child: Text('3')),
            SizedBox(height: 400.0, child: Text('4')),
            SizedBox(height: 400.0, child: Text('5')),
            SizedBox(height: 400.0, child: Text('6')),
          ],
        ),
      ),
    );

    expect(controller.offset, equals(1000.0));
    expect(tester.getTopLeft(find.text('4')).dy, equals(200.0));

    controller.jumpTo(300.0);
    await tester.pump();

    expect(controller.offset, equals(250.0));
    expect(tester.getTopLeft(find.text('2')).dy, equals(-50.0));

    controller.jumpTo(50.0);
    await tester.pump();

    expect(controller.offset, equals(50.0));
    expect(tester.getTopLeft(find.text('2')).dy, equals(150.0));
  });

  testWidgets('ListView can handle inserts at 0', (WidgetTester tester) async {
    final ScrollController controller = ScrollController();
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: ListView(
          controller: controller,
          children: const <Widget>[
            SizedBox(height: 400.0, child: Text('0')),
            SizedBox(height: 400.0, child: Text('1')),
            SizedBox(height: 400.0, child: Text('2')),
            SizedBox(height: 400.0, child: Text('3')),
          ],
        ),
      ),
    );
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsOneWidget);
    expect(find.text('2'), findsNothing);
    expect(find.text('3'), findsNothing);

    final Finder findItemA = find.descendant(of: find.byType(SizedBox), matching: find.text('A'));
    final Finder findItemB = find.descendant(of: find.byType(SizedBox), matching: find.text('B'));
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: ListView(
          controller: controller,
          children: const <Widget>[
            SizedBox(height: 10.0, child: Text('A')),
            SizedBox(height: 10.0, child: Text('B')),
            SizedBox(height: 400.0, child: Text('0')),
            SizedBox(height: 400.0, child: Text('1')),
            SizedBox(height: 400.0, child: Text('2')),
            SizedBox(height: 400.0, child: Text('3')),
          ],
        ),
      ),
    );
    expect(find.text('A'), findsOneWidget);
    expect(find.text('B'), findsOneWidget);
    expect(tester.getTopLeft(findItemA).dy, 0.0);
    expect(tester.getBottomRight(findItemA).dy, 10.0);
    expect(tester.getTopLeft(findItemB).dy, 10.0);
    expect(tester.getBottomRight(findItemB).dy, 20.0);


    controller.jumpTo(1200.0);
    await tester.pump();
    expect(find.text('A'), findsNothing);
    expect(find.text('B'), findsNothing);

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: ListView(
          controller: controller,
          children: const <Widget>[
            SizedBox(height: 200.0, child: Text('A')),
            SizedBox(height: 200.0, child: Text('B')),
            SizedBox(height: 400.0, child: Text('0')),
            SizedBox(height: 400.0, child: Text('1')),
            SizedBox(height: 400.0, child: Text('2')),
            SizedBox(height: 400.0, child: Text('3')),
          ],
        ),
      ),
    );
    expect(find.text('A'), findsNothing);
    expect(find.text('B'), findsNothing);

    // Scrolling to 0 causes items A and B to underflow (extend below
    // scrollOffset 0) because their heights have grown from 10 - 200.
    // RenderSliver list corrects the scroll offset in this case. Only item
    // B will become visible and item B's bottom edge will still appear
    // where it was when its height was 10.0.
    controller.jumpTo(0.0);
    await tester.pump();
    expect(find.text('B'), findsOneWidget);
    expect(controller.offset, greaterThan(0.0)); // RenderSliverList corrected the offset.
    expect(tester.getTopLeft(findItemB).dy, -180.0);
    expect(tester.getBottomRight(findItemB).dy, 20.0);


    controller.jumpTo(0.0);
    await tester.pump();
    expect(find.text('A'), findsOneWidget);
    expect(find.text('B'), findsOneWidget);
    expect(tester.getTopLeft(findItemA).dy, 0.0);
    expect(tester.getBottomRight(findItemA).dy, 200.0);
    expect(tester.getTopLeft(findItemB).dy, 200.0);
    expect(tester.getBottomRight(findItemB).dy, 400.0);
  });
}
