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

void main() {
  test('MaterialBannerThemeData copyWith, ==, hashCode basics', () {
    expect(const MaterialBannerThemeData(), const MaterialBannerThemeData().copyWith());
    expect(const MaterialBannerThemeData().hashCode, const MaterialBannerThemeData().copyWith().hashCode);
  });

  test('MaterialBannerThemeData null fields by default', () {
    const MaterialBannerThemeData bannerTheme = MaterialBannerThemeData();
    expect(bannerTheme.backgroundColor, null);
    expect(bannerTheme.surfaceTintColor, null);
    expect(bannerTheme.shadowColor, null);
    expect(bannerTheme.dividerColor, null);
    expect(bannerTheme.contentTextStyle, null);
    expect(bannerTheme.elevation, null);
    expect(bannerTheme.padding, null);
    expect(bannerTheme.leadingPadding, null);
  });

  testWidgets('Default MaterialBannerThemeData debugFillProperties', (WidgetTester tester) async {
    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
    const MaterialBannerThemeData().debugFillProperties(builder);

    final List<String> description = builder.properties
      .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
      .map((DiagnosticsNode node) => node.toString())
      .toList();

    expect(description, <String>[]);
  });

  testWidgets('MaterialBannerThemeData implements debugFillProperties', (WidgetTester tester) async {
    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
    const MaterialBannerThemeData(
      backgroundColor: Color(0xfffffff0),
      surfaceTintColor: Color(0xfffffff1),
      shadowColor: Color(0xfffffff2),
      dividerColor: Color(0xfffffff3),
      contentTextStyle: TextStyle(color: Color(0xfffffff4)),
      elevation: 4.0,
      padding: EdgeInsets.all(20.0),
      leadingPadding: EdgeInsets.only(left: 8.0),
    ).debugFillProperties(builder);

    final List<String> description = builder.properties
      .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
      .map((DiagnosticsNode node) => node.toString())
      .toList();

    expect(description, <String>[
      'backgroundColor: Color(0xfffffff0)',
      'surfaceTintColor: Color(0xfffffff1)',
      'shadowColor: Color(0xfffffff2)',
      'dividerColor: Color(0xfffffff3)',
      'contentTextStyle: TextStyle(inherit: true, color: Color(0xfffffff4))',
      'elevation: 4.0',
      'padding: EdgeInsets.all(20.0)',
      'leadingPadding: EdgeInsets(8.0, 0.0, 0.0, 0.0)',
    ]);
  });

  testWidgets('Passing no MaterialBannerThemeData returns defaults', (WidgetTester tester) async {
    const String contentText = 'Content';
    final ThemeData theme = ThemeData(useMaterial3: true);
    late final ThemeData localizedTheme;

    await tester.pumpWidget(MaterialApp(
      theme: theme,
      builder:(BuildContext context, Widget? child) {
        localizedTheme = Theme.of(context);
        return child!;
      },
      home: Scaffold(
        body: MaterialBanner(
          content: const Text(contentText),
          leading: const Icon(Icons.umbrella),
          actions: <Widget>[
            TextButton(
              child: const Text('Action'),
              onPressed: () { },
            ),
          ],
        ),
      ),
    ));

    final Material material = _getMaterialFromText(tester, contentText);
    expect(material.color, theme.colorScheme.surface);
    expect(material.surfaceTintColor, theme.colorScheme.surfaceTint);
    expect(material.shadowColor, null);
    expect(material.elevation, 0.0);

    final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
    expect(
      content.text.style,
      localizedTheme.textTheme.bodyMedium,
    );

    final Offset rowTopLeft = tester.getTopLeft(find.byType(Row));
    final Offset materialTopLeft = tester.getTopLeft(_materialFinder());
    final Offset leadingTopLeft = tester.getTopLeft(find.byIcon(Icons.umbrella));
    expect(rowTopLeft.dy - materialTopLeft.dy, 2.0); // Default single line top padding.
    expect(rowTopLeft.dx - materialTopLeft.dx, 16.0); // Default single line start padding.
    expect(leadingTopLeft.dy - materialTopLeft.dy, 16); // Default leading padding.
    expect(leadingTopLeft.dx - materialTopLeft.dx, 16); // Default leading padding.

    final Divider divider = tester.widget<Divider>(find.byType(Divider));
    expect(divider.color, theme.colorScheme.outlineVariant);
  });

  testWidgets('Passing no MaterialBannerThemeData returns defaults when presented by ScaffoldMessenger', (WidgetTester tester) async {
    const String contentText = 'Content';
    const Key tapTarget = Key('tap-target');
    final ThemeData theme = ThemeData(useMaterial3: true);
    late final ThemeData localizedTheme;

    await tester.pumpWidget(MaterialApp(
      theme: theme,
      home: Scaffold(
        body: Builder(
          builder: (BuildContext context) {
            localizedTheme = Theme.of(context);
            return GestureDetector(
              key: tapTarget,
              onTap: () {
                ScaffoldMessenger.of(context).showMaterialBanner(MaterialBanner(
                  content: const Text(contentText),
                  leading: const Icon(Icons.umbrella),
                  actions: <Widget>[
                    TextButton(
                      child: const Text('Action'),
                      onPressed: () { },
                    ),
                  ],
                ));
              },
              behavior: HitTestBehavior.opaque,
              child: const SizedBox(
                height: 100.0,
                width: 100.0,
              ),
            );
          },
        ),
      ),
    ));
    await tester.tap(find.byKey(tapTarget));
    await tester.pumpAndSettle();

    final Material material = _getMaterialFromText(tester, contentText);
    expect(material.color, theme.colorScheme.surface);
    expect(material.surfaceTintColor, theme.colorScheme.surfaceTint);
    expect(material.shadowColor, null);
    expect(material.elevation, 0.0);

    final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
    expect(
      content.text.style,
      localizedTheme.textTheme.bodyMedium,
    );

    final Offset rowTopLeft = tester.getTopLeft(find.byType(Row));
    final Offset materialTopLeft = tester.getTopLeft(_materialFinder());
    final Offset leadingTopLeft = tester.getTopLeft(find.byIcon(Icons.umbrella));
    expect(rowTopLeft.dy - materialTopLeft.dy, 2.0); // Default single line top padding.
    expect(rowTopLeft.dx - materialTopLeft.dx, 16.0); // Default single line start padding.
    expect(leadingTopLeft.dy - materialTopLeft.dy, 16); // Default leading padding.
    expect(leadingTopLeft.dx - materialTopLeft.dx, 16); // Default leading padding.

    final Divider divider = tester.widget<Divider>(find.byType(Divider));
    expect(divider.color, theme.colorScheme.outlineVariant);
  });

  testWidgets('MaterialBanner uses values from MaterialBannerThemeData', (WidgetTester tester) async {
    final MaterialBannerThemeData bannerTheme = _bannerTheme();
    const String contentText = 'Content';
    await tester.pumpWidget(MaterialApp(
      theme: ThemeData(bannerTheme: bannerTheme),
      home: Scaffold(
        body: MaterialBanner(
          leading: const Icon(Icons.ac_unit),
          content: const Text(contentText),
          actions: <Widget>[
            TextButton(
              child: const Text('Action'),
              onPressed: () { },
            ),
          ],
        ),
      ),
    ));

    final Material material = _getMaterialFromText(tester, contentText);
    expect(material.color, bannerTheme.backgroundColor);
    expect(material.surfaceTintColor, bannerTheme.surfaceTintColor);
    expect(material.shadowColor, bannerTheme.shadowColor);
    expect(material.elevation, bannerTheme.elevation);

    final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
    expect(content.text.style, bannerTheme.contentTextStyle);

    final Offset contentTopLeft = tester.getTopLeft(_textFinder(contentText));
    final Offset materialTopLeft = tester.getTopLeft(_materialFinder());
    final Offset leadingTopLeft = tester.getTopLeft(find.byIcon(Icons.ac_unit));
    expect(contentTopLeft.dy - materialTopLeft.dy, 24);
    expect(contentTopLeft.dx - materialTopLeft.dx, 41);
    expect(leadingTopLeft.dy - materialTopLeft.dy, 19);
    expect(leadingTopLeft.dx - materialTopLeft.dx, 11);

    expect(find.byType(Divider), findsNothing);
  });

  testWidgets('MaterialBanner uses values from MaterialBannerThemeData when presented by ScaffoldMessenger', (WidgetTester tester) async {
    final MaterialBannerThemeData bannerTheme = _bannerTheme();
    const String contentText = 'Content';
    const Key tapTarget = Key('tap-target');
    await tester.pumpWidget(MaterialApp(
      theme: ThemeData(bannerTheme: bannerTheme),
      home: Scaffold(
        body: Builder(
          builder: (BuildContext context) {
            return GestureDetector(
              key: tapTarget,
              onTap: () {
                ScaffoldMessenger.of(context).showMaterialBanner(MaterialBanner(
                  leading: const Icon(Icons.ac_unit),
                  content: const Text(contentText),
                  actions: <Widget>[
                    TextButton(
                      child: const Text('Action'),
                      onPressed: () { },
                    ),
                  ],
                ));
              },
              behavior: HitTestBehavior.opaque,
              child: const SizedBox(
                height: 100.0,
                width: 100.0,
              ),
            );
          },
        ),
      ),
    ));
    await tester.tap(find.byKey(tapTarget));
    await tester.pumpAndSettle();

    final Material material = _getMaterialFromText(tester, contentText);
    expect(material.color, bannerTheme.backgroundColor);
    expect(material.surfaceTintColor, bannerTheme.surfaceTintColor);
    expect(material.shadowColor, bannerTheme.shadowColor);
    expect(material.elevation, bannerTheme.elevation);

    final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
    expect(content.text.style, bannerTheme.contentTextStyle);

    final Offset contentTopLeft = tester.getTopLeft(_textFinder(contentText));
    final Offset materialTopLeft = tester.getTopLeft(_materialFinder());
    final Offset leadingTopLeft = tester.getTopLeft(find.byIcon(Icons.ac_unit));
    expect(contentTopLeft.dy - materialTopLeft.dy, 24);
    expect(contentTopLeft.dx - materialTopLeft.dx, 41);
    expect(leadingTopLeft.dy - materialTopLeft.dy, 19);
    expect(leadingTopLeft.dx - materialTopLeft.dx, 11);

    expect(find.byType(Divider), findsNothing);
  });

  testWidgets('MaterialBanner widget properties take priority over theme', (WidgetTester tester) async {
    const Color backgroundColor = Colors.purple;
    const Color surfaceTintColor = Colors.red;
    const Color shadowColor = Colors.orange;
    const TextStyle textStyle = TextStyle(color: Colors.green);
    final MaterialBannerThemeData bannerTheme = _bannerTheme();
    const String contentText = 'Content';

    await tester.pumpWidget(MaterialApp(
      theme: ThemeData(bannerTheme: bannerTheme),
      home: Scaffold(
        body: MaterialBanner(
          backgroundColor: backgroundColor,
          surfaceTintColor: surfaceTintColor,
          shadowColor: shadowColor,
          elevation: 6.0,
          leading: const Icon(Icons.ac_unit),
          contentTextStyle: textStyle,
          content: const Text(contentText),
          padding: const EdgeInsets.all(10),
          leadingPadding: const EdgeInsets.all(12),
          actions: <Widget>[
            TextButton(
              child: const Text('Action'),
              onPressed: () { },
            ),
          ],
        ),
      ),
    ));

    final Material material = _getMaterialFromText(tester, contentText);
    expect(material.color, backgroundColor);
    expect(material.surfaceTintColor, surfaceTintColor);
    expect(material.shadowColor, shadowColor);
    expect(material.elevation, 6.0);

    final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
    expect(content.text.style, textStyle);

    final Offset contentTopLeft = tester.getTopLeft(_textFinder(contentText));
    final Offset materialTopLeft = tester.getTopLeft(_materialFinder());
    final Offset leadingTopLeft = tester.getTopLeft(find.byIcon(Icons.ac_unit));
    expect(contentTopLeft.dy - materialTopLeft.dy, 29);
    expect(contentTopLeft.dx - materialTopLeft.dx, 58);
    expect(leadingTopLeft.dy - materialTopLeft.dy, 24);
    expect(leadingTopLeft.dx - materialTopLeft.dx, 22);

    expect(find.byType(Divider), findsNothing);
  });

  testWidgets('MaterialBanner widget properties take priority over theme when presented by ScaffoldMessenger', (WidgetTester tester) async {
    const Color backgroundColor = Colors.purple;
    const double elevation = 6.0;
    const TextStyle textStyle = TextStyle(color: Colors.green);
    final MaterialBannerThemeData bannerTheme = _bannerTheme();
    const String contentText = 'Content';
    const Key tapTarget = Key('tap-target');

    await tester.pumpWidget(MaterialApp(
      theme: ThemeData(bannerTheme: bannerTheme),
      home: Scaffold(
        body: Builder(
          builder: (BuildContext context) {
            return GestureDetector(
              key: tapTarget,
              onTap: () {
                ScaffoldMessenger.of(context).showMaterialBanner(MaterialBanner(
                  backgroundColor: backgroundColor,
                  elevation: elevation,
                  leading: const Icon(Icons.ac_unit),
                  contentTextStyle: textStyle,
                  content: const Text(contentText),
                  padding: const EdgeInsets.all(10),
                  leadingPadding: const EdgeInsets.all(12),
                  actions: <Widget>[
                    TextButton(
                      child: const Text('Action'),
                      onPressed: () { },
                    ),
                  ],
                ));
              },
              behavior: HitTestBehavior.opaque,
              child: const SizedBox(
                height: 100.0,
                width: 100.0,
              ),
            );
          },
        ),
      ),
    ));
    await tester.tap(find.byKey(tapTarget));
    await tester.pumpAndSettle();

    final Material material = _getMaterialFromText(tester, contentText);
    expect(material.color, backgroundColor);
    expect(material.elevation, elevation);

    final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
    expect(content.text.style, textStyle);

    final Offset contentTopLeft = tester.getTopLeft(_textFinder(contentText));
    final Offset materialTopLeft = tester.getTopLeft(_materialFinder());
    final Offset leadingTopLeft = tester.getTopLeft(find.byIcon(Icons.ac_unit));
    expect(contentTopLeft.dy - materialTopLeft.dy, 29);
    expect(contentTopLeft.dx - materialTopLeft.dx, 58);
    expect(leadingTopLeft.dy - materialTopLeft.dy, 24);
    expect(leadingTopLeft.dx - materialTopLeft.dx, 22);

    expect(find.byType(Divider), findsNothing);
  });

  testWidgets('MaterialBanner uses color scheme when necessary', (WidgetTester tester) async {
    final ColorScheme colorScheme = const ColorScheme.light().copyWith(surface: Colors.purple);
    const String contentText = 'Content';
    await tester.pumpWidget(MaterialApp(
      theme: ThemeData(colorScheme: colorScheme),
      home: Scaffold(
        body: MaterialBanner(
          content: const Text(contentText),
          actions: <Widget>[
            TextButton(
              child: const Text('Action'),
              onPressed: () { },
            ),
          ],
        ),
      ),
    ));

    final Material material = _getMaterialFromText(tester, contentText);
    expect(material.color, colorScheme.surface);
  });

  testWidgets('MaterialBanner uses color scheme when necessary when presented by ScaffoldMessenger', (WidgetTester tester) async {
    final ColorScheme colorScheme = const ColorScheme.light().copyWith(surface: Colors.purple);
    const String contentText = 'Content';
    const Key tapTarget = Key('tap-target');
    await tester.pumpWidget(MaterialApp(
      theme: ThemeData(colorScheme: colorScheme),
      home: Scaffold(
        body: Builder(
          builder: (BuildContext context) {
            return GestureDetector(
              key: tapTarget,
              onTap: () {
                ScaffoldMessenger.of(context).showMaterialBanner(MaterialBanner(
                  content: const Text(contentText),
                  actions: <Widget>[
                    TextButton(
                      child: const Text('Action'),
                      onPressed: () { },
                    ),
                  ],
                ));
              },
              behavior: HitTestBehavior.opaque,
              child: const SizedBox(
                height: 100.0,
                width: 100.0,
              ),
            );
          },
        ),
      ),
    ));
    await tester.tap(find.byKey(tapTarget));
    await tester.pumpAndSettle();

    final Material material = _getMaterialFromText(tester, contentText);
    expect(material.color, colorScheme.surface);
  });

  group('Material 2', () {
    // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
    // is turned on by default, these tests can be removed.

    testWidgets('Passing no MaterialBannerThemeData returns defaults', (WidgetTester tester) async {
      const String contentText = 'Content';

      await tester.pumpWidget(MaterialApp(
        home: Scaffold(
          body: MaterialBanner(
            content: const Text(contentText),
            leading: const Icon(Icons.umbrella),
            actions: <Widget>[
              TextButton(
                child: const Text('Action'),
                onPressed: () { },
              ),
            ],
          ),
        ),
      ));

      final Material material = _getMaterialFromText(tester, contentText);
      expect(material.color, const Color(0xffffffff));
      expect(material.surfaceTintColor, null);
      expect(material.shadowColor, null);
      expect(material.elevation, 0.0);

      final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
      // Default value for ThemeData.typography is Typography.material2014()
      expect(
        content.text.style,
        Typography.material2014().englishLike.bodyText2!.merge(
          Typography.material2014().black.bodyText2,
        ),
      );

      final Offset rowTopLeft = tester.getTopLeft(find.byType(Row));
      final Offset materialTopLeft = tester.getTopLeft(_materialFinder());
      final Offset leadingTopLeft = tester.getTopLeft(find.byIcon(Icons.umbrella));
      expect(rowTopLeft.dy - materialTopLeft.dy, 2.0); // Default single line top padding.
      expect(rowTopLeft.dx - materialTopLeft.dx, 16.0); // Default single line start padding.
      expect(leadingTopLeft.dy - materialTopLeft.dy, 16); // Default leading padding.
      expect(leadingTopLeft.dx - materialTopLeft.dx, 16); // Default leading padding.

      final Divider divider = tester.widget<Divider>(find.byType(Divider));
      expect(divider.color, null);
    });

    testWidgets('Passing no MaterialBannerThemeData returns defaults when presented by ScaffoldMessenger', (WidgetTester tester) async {
      const String contentText = 'Content';
      const Key tapTarget = Key('tap-target');

      await tester.pumpWidget(MaterialApp(
        home: Scaffold(
          body: Builder(
            builder: (BuildContext context) {
              return GestureDetector(
                key: tapTarget,
                onTap: () {
                  ScaffoldMessenger.of(context).showMaterialBanner(MaterialBanner(
                    content: const Text(contentText),
                    leading: const Icon(Icons.umbrella),
                    actions: <Widget>[
                      TextButton(
                        child: const Text('Action'),
                        onPressed: () { },
                      ),
                    ],
                  ));
                },
                behavior: HitTestBehavior.opaque,
                child: const SizedBox(
                  height: 100.0,
                  width: 100.0,
                ),
              );
            },
          ),
        ),
      ));
      await tester.tap(find.byKey(tapTarget));
      await tester.pumpAndSettle();

      final Material material = _getMaterialFromText(tester, contentText);
      expect(material.color, const Color(0xffffffff));
      expect(material.surfaceTintColor, null);
      expect(material.shadowColor, null);
      expect(material.elevation, 0.0);

      final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
      // Default value for ThemeData.typography is Typography.material2014()
      expect(
        content.text.style,
        Typography.material2014().englishLike.bodyText2!.merge(
          Typography.material2014().black.bodyText2,
        ),
      );

      final Offset rowTopLeft = tester.getTopLeft(find.byType(Row));
      final Offset materialTopLeft = tester.getTopLeft(_materialFinder());
      final Offset leadingTopLeft = tester.getTopLeft(find.byIcon(Icons.umbrella));
      expect(rowTopLeft.dy - materialTopLeft.dy, 2.0); // Default single line top padding.
      expect(rowTopLeft.dx - materialTopLeft.dx, 16.0); // Default single line start padding.
      expect(leadingTopLeft.dy - materialTopLeft.dy, 16); // Default leading padding.
      expect(leadingTopLeft.dx - materialTopLeft.dx, 16); // Default leading padding.

      final Divider divider = tester.widget<Divider>(find.byType(Divider));
      expect(divider.color, null);
    });
  });
}

MaterialBannerThemeData _bannerTheme() {
  return const MaterialBannerThemeData(
    backgroundColor: Colors.orange,
    surfaceTintColor: Colors.yellow,
    shadowColor: Colors.red,
    dividerColor: Colors.green,
    contentTextStyle: TextStyle(color: Colors.pink),
    elevation: 4.0,
    padding: EdgeInsets.all(5),
    leadingPadding: EdgeInsets.all(6),
  );
}

Material _getMaterialFromText(WidgetTester tester, String text) {
  return tester.widget<Material>(find.widgetWithText(Material, text).first);
}

Finder _materialFinder() {
  return find.descendant(of: find.byType(MaterialBanner), matching: find.byType(Material)).first;
}

RenderParagraph _getTextRenderObjectFromDialog(WidgetTester tester, String text) {
  return tester.element<StatelessElement>(_textFinder(text)).renderObject! as RenderParagraph;
}

Finder _textFinder(String text) {
  return find.descendant(of: find.byType(MaterialBanner), matching: find.text(text));
}
