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

import 'states.dart';

const Duration _frameDuration = const Duration(milliseconds: 100);

void main() {
  testWidgets('PageView control test', (WidgetTester tester) async {
    final List<String> log = <String>[];

    await tester.pumpWidget(new PageView(
      children: kStates.map<Widget>((String state) {
        return new GestureDetector(
          onTap: () {
            log.add(state);
          },
          child: new Container(
            height: 200.0,
            color: const Color(0xFF0000FF),
            child: new Text(state),
          ),
        );
      }).toList(),
    ));

    await tester.tap(find.text('Alabama'));
    expect(log, equals(<String>['Alabama']));
    log.clear();

    expect(find.text('Alaska'), findsNothing);

    await tester.drag(find.byType(PageView), const Offset(-20.0, 0.0));
    await tester.pump();

    expect(find.text('Alabama'), findsOneWidget);
    expect(find.text('Alaska'), findsOneWidget);
    expect(find.text('Arizona'), findsNothing);

    await tester.pumpAndSettle(_frameDuration);

    expect(find.text('Alabama'), findsOneWidget);
    expect(find.text('Alaska'), findsNothing);

    await tester.drag(find.byType(PageView), const Offset(-401.0, 0.0));
    await tester.pumpAndSettle(_frameDuration);

    expect(find.text('Alabama'), findsNothing);
    expect(find.text('Alaska'), findsOneWidget);
    expect(find.text('Arizona'), findsNothing);

    await tester.tap(find.text('Alaska'));
    expect(log, equals(<String>['Alaska']));
    log.clear();

    await tester.fling(find.byType(PageView), const Offset(-200.0, 0.0), 1000.0);
    await tester.pumpAndSettle(_frameDuration);

    expect(find.text('Alabama'), findsNothing);
    expect(find.text('Alaska'), findsNothing);
    expect(find.text('Arizona'), findsOneWidget);

    await tester.fling(find.byType(PageView), const Offset(200.0, 0.0), 1000.0);
    await tester.pumpAndSettle(_frameDuration);

    expect(find.text('Alabama'), findsNothing);
    expect(find.text('Alaska'), findsOneWidget);
    expect(find.text('Arizona'), findsNothing);
  });

  testWidgets('PageView does not squish when overscrolled',
      (WidgetTester tester) async {
    await tester.pumpWidget(new MaterialApp(
      theme: new ThemeData(platform: TargetPlatform.iOS),
      home: new PageView(
        children: new List<Widget>.generate(10, (int i) {
          return new Container(
            key: new ValueKey<int>(i),
            color: const Color(0xFF0000FF),
          );
        }),
      ),
    ));

    Size sizeOf(int i) => tester.getSize(find.byKey(new ValueKey<int>(i)));
    double leftOf(int i) => tester.getTopLeft(find.byKey(new ValueKey<int>(i))).dx;

    expect(leftOf(0), equals(0.0));
    expect(sizeOf(0), equals(const Size(800.0, 600.0)));

    // Going into overscroll.
    await tester.drag(find.byType(PageView), const Offset(100.0, 0.0));
    await tester.pump();

    expect(leftOf(0), greaterThan(0.0));
    expect(sizeOf(0), equals(const Size(800.0, 600.0)));

    // Easing overscroll past overscroll limit.
    await tester.drag(find.byType(PageView), const Offset(-200.0, 0.0));
    await tester.pump();

    expect(leftOf(0), lessThan(0.0));
    expect(sizeOf(0), equals(const Size(800.0, 600.0)));
  });

  testWidgets('PageController control test', (WidgetTester tester) async {
    final PageController controller = new PageController(initialPage: 4);

    await tester.pumpWidget(new Center(
      child: new SizedBox(
        width: 600.0,
        height: 400.0,
        child: new PageView(
          controller: controller,
          children: kStates.map<Widget>((String state) => new Text(state)).toList(),
        ),
      ),
    ));

    expect(find.text('California'), findsOneWidget);

    controller.nextPage(duration: const Duration(milliseconds: 150), curve: Curves.ease);
    await tester.pumpAndSettle(const Duration(milliseconds: 100));

    expect(find.text('Colorado'), findsOneWidget);

    await tester.pumpWidget(new Center(
      child: new SizedBox(
        width: 300.0,
        height: 400.0,
        child: new PageView(
          controller: controller,
          children: kStates.map<Widget>((String state) => new Text(state)).toList(),
        ),
      ),
    ));

    expect(find.text('Colorado'), findsOneWidget);

    controller.previousPage(duration: const Duration(milliseconds: 150), curve: Curves.ease);
    await tester.pumpAndSettle(const Duration(milliseconds: 100));

    expect(find.text('California'), findsOneWidget);
  });

  testWidgets('PageController page stability', (WidgetTester tester) async {
    await tester.pumpWidget(new Center(
      child: new SizedBox(
        width: 600.0,
        height: 400.0,
        child: new PageView(
          children: kStates.map<Widget>((String state) => new Text(state)).toList(),
        ),
      ),
    ));

    expect(find.text('Alabama'), findsOneWidget);

    await tester.drag(find.byType(PageView), const Offset(-1250.0, 0.0));
    await tester.pumpAndSettle(const Duration(milliseconds: 100));

    expect(find.text('Arizona'), findsOneWidget);

    await tester.pumpWidget(new Center(
      child: new SizedBox(
        width: 250.0,
        height: 100.0,
        child: new PageView(
          children: kStates.map<Widget>((String state) => new Text(state)).toList(),
        ),
      ),
    ));

    expect(find.text('Arizona'), findsOneWidget);

    await tester.pumpWidget(new Center(
      child: new SizedBox(
        width: 450.0,
        height: 400.0,
        child: new PageView(
          children: kStates.map<Widget>((String state) => new Text(state)).toList(),
        ),
      ),
    ));

    expect(find.text('Arizona'), findsOneWidget);
  });

  testWidgets('PageView in zero-size container', (WidgetTester tester) async {
    await tester.pumpWidget(new Center(
      child: new SizedBox(
        width: 0.0,
        height: 0.0,
        child: new PageView(
          children: kStates.map<Widget>((String state) => new Text(state)).toList(),
        ),
      ),
    ));

    expect(find.text('Alabama'), findsOneWidget);

    await tester.pumpWidget(new Center(
      child: new SizedBox(
        width: 200.0,
        height: 200.0,
        child: new PageView(
          children: kStates.map<Widget>((String state) => new Text(state)).toList(),
        ),
      ),
    ));

    expect(find.text('Alabama'), findsOneWidget);
  });

  testWidgets('Page changes at halfway point', (WidgetTester tester) async {
    final List<int> log = <int>[];
    await tester.pumpWidget(new PageView(
      onPageChanged: log.add,
      children: kStates.map<Widget>((String state) => new Text(state)).toList(),
    ));

    expect(log, isEmpty);

    final TestGesture gesture =
        await tester.startGesture(const Offset(100.0, 100.0));
    // The page view is 800.0 wide, so this move is just short of halfway.
    await gesture.moveBy(const Offset(-380.0, 0.0));

    expect(log, isEmpty);

    // We've crossed the halfway mark.
    await gesture.moveBy(const Offset(-40.0, 0.0));

    expect(log, equals(const <int>[1]));
    log.clear();

    // Moving a bit more should not generate redundant notifications.
    await gesture.moveBy(const Offset(-40.0, 0.0));

    expect(log, isEmpty);

    await gesture.moveBy(const Offset(-40.0, 0.0));
    await tester.pump();

    await gesture.moveBy(const Offset(-40.0, 0.0));
    await tester.pump();

    await gesture.moveBy(const Offset(-40.0, 0.0));
    await tester.pump();

    expect(log, isEmpty);

    await gesture.up();
    await tester.pumpAndSettle();

    expect(log, isEmpty);

    expect(find.text('Alabama'), findsNothing);
    expect(find.text('Alaska'), findsOneWidget);
  });

  testWidgets('PageView viewportFraction', (WidgetTester tester) async {
    PageController controller = new PageController(viewportFraction: 7/8);

    Widget build(PageController controller) {
      return new PageView.builder(
        controller: controller,
        itemCount: kStates.length,
        itemBuilder: (BuildContext context, int index) {
          return new Container(
            height: 200.0,
            color: index % 2 == 0
                ? const Color(0xFF0000FF)
                : const Color(0xFF00FF00),
            child: new Text(kStates[index]),
          );
        },
      );
    }

    await tester.pumpWidget(build(controller));

    expect(tester.getTopLeft(find.text('Alabama')), const Offset(50.0, 0.0));
    expect(tester.getTopLeft(find.text('Alaska')), const Offset(750.0, 0.0));

    controller.jumpToPage(10);
    await tester.pump();

    expect(tester.getTopLeft(find.text('Georgia')), const Offset(-650.0, 0.0));
    expect(tester.getTopLeft(find.text('Hawaii')), const Offset(50.0, 0.0));
    expect(tester.getTopLeft(find.text('Idaho')), const Offset(750.0, 0.0));

    controller = new PageController(viewportFraction: 39/40);

    await tester.pumpWidget(build(controller));

    expect(tester.getTopLeft(find.text('Georgia')), const Offset(-770.0, 0.0));
    expect(tester.getTopLeft(find.text('Hawaii')), const Offset(10.0, 0.0));
    expect(tester.getTopLeft(find.text('Idaho')), const Offset(790.0, 0.0));
  });

  testWidgets('PageView small viewportFraction', (WidgetTester tester) async {
    final PageController controller = new PageController(viewportFraction: 1/8);

    Widget build(PageController controller) {
      return new PageView.builder(
        controller: controller,
        itemCount: kStates.length,
        itemBuilder: (BuildContext context, int index) {
          return new Container(
            height: 200.0,
            color: index % 2 == 0
                ? const Color(0xFF0000FF)
                : const Color(0xFF00FF00),
            child: new Text(kStates[index]),
          );
        },
      );
    }

    await tester.pumpWidget(build(controller));

    expect(tester.getTopLeft(find.text('Alabama')), const Offset(350.0, 0.0));
    expect(tester.getTopLeft(find.text('Alaska')), const Offset(450.0, 0.0));
    expect(tester.getTopLeft(find.text('Arizona')), const Offset(550.0, 0.0));
    expect(tester.getTopLeft(find.text('Arkansas')), const Offset(650.0, 0.0));
    expect(tester.getTopLeft(find.text('California')), const Offset(750.0, 0.0));

    controller.jumpToPage(10);
    await tester.pump();

    expect(tester.getTopLeft(find.text('Connecticut')), const Offset(-50.0, 0.0));
    expect(tester.getTopLeft(find.text('Delaware')), const Offset(50.0, 0.0));
    expect(tester.getTopLeft(find.text('Florida')), const Offset(150.0, 0.0));
    expect(tester.getTopLeft(find.text('Georgia')), const Offset(250.0, 0.0));
    expect(tester.getTopLeft(find.text('Hawaii')), const Offset(350.0, 0.0));
    expect(tester.getTopLeft(find.text('Idaho')), const Offset(450.0, 0.0));
    expect(tester.getTopLeft(find.text('Illinois')), const Offset(550.0, 0.0));
    expect(tester.getTopLeft(find.text('Indiana')), const Offset(650.0, 0.0));
    expect(tester.getTopLeft(find.text('Iowa')), const Offset(750.0, 0.0));
  });

  testWidgets('PageView large viewportFraction', (WidgetTester tester) async {
    final PageController controller =
        new PageController(viewportFraction: 5/4);

    Widget build(PageController controller) {
      return new PageView.builder(
        controller: controller,
        itemCount: kStates.length,
        itemBuilder: (BuildContext context, int index) {
          return new Container(
            height: 200.0,
            color: index % 2 == 0
                ? const Color(0xFF0000FF)
                : const Color(0xFF00FF00),
            child: new Text(kStates[index]),
          );
        },
      );
    }

    await tester.pumpWidget(build(controller));

    expect(tester.getTopLeft(find.text('Alabama')), const Offset(-100.0, 0.0));
    expect(tester.getBottomRight(find.text('Alabama')), const Offset(900.0, 600.0));

    controller.jumpToPage(10);
    await tester.pump();

    expect(tester.getTopLeft(find.text('Hawaii')), const Offset(-100.0, 0.0));
  });

  testWidgets('PageView does not report page changed on overscroll',
      (WidgetTester tester) async {
    final PageController controller = new PageController(
      initialPage: kStates.length - 1,
    );
    int changeIndex = 0;
    Widget build() {
      return new PageView(
        children:
            kStates.map<Widget>((String state) => new Text(state)).toList(),
        controller: controller,
        onPageChanged: (int page) {
          changeIndex = page;
        },
      );
    }

    await tester.pumpWidget(build());
    controller.jumpToPage(kStates.length * 2); // try to move beyond max range
    // change index should be zero, shouldn't fire onPageChanged
    expect(changeIndex, 0);
    await tester.pump();
    expect(changeIndex, 0);
  });

  testWidgets('PageView can restore page',
      (WidgetTester tester) async {
    final PageController controller = new PageController();
    final PageStorageBucket bucket = new PageStorageBucket();
    await tester.pumpWidget(
      new PageStorage(
        bucket: bucket,
        child: new PageView(
          key: const PageStorageKey<String>('PageView'),
          controller: controller,
          children: <Widget>[
            const Placeholder(),
            const Placeholder(),
            const Placeholder(),
          ],
        ),
      ),
    );
    expect(controller.page, 0);
    controller.jumpToPage(2);
    expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 1);
    expect(controller.page, 2);
    await tester.pumpWidget(
      new PageStorage(
        bucket: bucket,
        child: new Container(),
      ),
    );
    expect(() => controller.page, throwsAssertionError);
    await tester.pumpWidget(
      new PageStorage(
        bucket: bucket,
        child: new PageView(
          key: const PageStorageKey<String>('PageView'),
          controller: controller,
          children: <Widget>[
            const Placeholder(),
            const Placeholder(),
            const Placeholder(),
          ],
        ),
      ),
    );
    expect(controller.page, 2);

    final PageController controller2 = new PageController(keepPage: false);
    await tester.pumpWidget(
      new PageStorage(
        bucket: bucket,
        child: new PageView(
          key: const PageStorageKey<String>('Check it again against your list and see consistency!'),
          controller: controller2,
          children: <Widget>[
            const Placeholder(),
            const Placeholder(),
            const Placeholder(),
          ],
        ),
      ),
    );
    expect(controller2.page, 0);
  });
}