mirrored_repositories/awesome-flutter/examples/flutterfire/packages/firebase_analytics/firebase_analytics
// 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 'dart:async';

import 'package:flutter/services.dart';
import 'package:mockito/mockito.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:flutter/widgets.dart';

import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  group('FirebaseAnalyticsObserver', () {
    late MockFirebaseAnalytics analytics;
    late FirebaseAnalyticsObserver observer;
    final List<String> printLog = <String>[];

    void overridePrint(void Function() func) {
      final ZoneSpecification spec =
          ZoneSpecification(print: (_, __, ___, String msg) {
        // Add to log instead of printing to stdout
        printLog.add(msg);
      });
      return Zone.current.fork(specification: spec).run(func);
    }

    setUp(() {
      printLog.clear();
      analytics = MockFirebaseAnalytics();
      observer = FirebaseAnalyticsObserver(analytics: analytics);
      when(analytics.setCurrentScreen(screenName: anyNamed('screenName')))
          .thenAnswer((Invocation invocation) => Future<void>.value());
    });

    test('setCurrentScreen on route pop', () {
      final PageRoute<dynamic> route = MockPageRoute();
      final PageRoute<dynamic> previousRoute = MockPageRoute();
      when(previousRoute.settings)
          .thenReturn(const RouteSettings(name: 'previousRoute'));

      observer.didPop(route, previousRoute);

      verify(analytics.setCurrentScreen(screenName: 'previousRoute')).called(1);
    });

    test('setCurrentScreen on route push', () {
      final PageRoute<dynamic> route = MockPageRoute();
      final PageRoute<dynamic> previousRoute = MockPageRoute();
      when(route.settings).thenReturn(const RouteSettings(name: 'route'));

      observer.didPush(route, previousRoute);

      verify(analytics.setCurrentScreen(screenName: 'route')).called(1);
    });

    test('setCurrentScreen on route pushReplacement', () {
      final PageRoute<dynamic> route = MockPageRoute();
      final PageRoute<dynamic> previousRoute = MockPageRoute();
      when(route.settings).thenReturn(const RouteSettings(name: 'route'));

      observer.didReplace(newRoute: route, oldRoute: previousRoute);

      verify(analytics.setCurrentScreen(screenName: 'route')).called(1);
    });

    test('uses nameExtractor', () {
      observer = FirebaseAnalyticsObserver(
        analytics: analytics,
        nameExtractor: (RouteSettings settings) => 'foo',
      );
      final PageRoute<dynamic> route = MockPageRoute();
      final PageRoute<dynamic> previousRoute = MockPageRoute();

      observer.didPush(route, previousRoute);

      verify(analytics.setCurrentScreen(screenName: 'foo')).called(1);
    });

    test('handles only ${PlatformException}s', () async {
      observer = FirebaseAnalyticsObserver(
        analytics: analytics,
        nameExtractor: (RouteSettings settings) => 'foo',
      );

      final PageRoute<dynamic> route = MockPageRoute();
      final PageRoute<dynamic> previousRoute = MockPageRoute();

      // Throws non-PlatformExceptions
      when(analytics.setCurrentScreen(screenName: anyNamed('screenName')))
          .thenThrow(ArgumentError());

      // TODO: Reenable the line below when the issue is fixed.
      // https://github.com/FirebaseExtended/flutterfire/issues/2850
      // expect(() => observer.didPush(route, previousRoute), throwsArgumentError);

      // Print PlatformExceptions
      Future<void> throwPlatformException() async =>
          throw PlatformException(code: 'a');

      when(analytics.setCurrentScreen(screenName: anyNamed('screenName')))
          .thenAnswer((Invocation invocation) => throwPlatformException());

      overridePrint(() => observer.didPush(route, previousRoute));

      await pumpEventQueue();
      expect(
        printLog,
        <String>['$FirebaseAnalyticsObserver: ${PlatformException(code: 'a')}'],
      );
    });

    test('runs onError', () async {
      PlatformException? passedException;

      void handleError(PlatformException error) {
        passedException = error;
      }

      observer = FirebaseAnalyticsObserver(
        analytics: analytics,
        nameExtractor: (RouteSettings settings) => 'foo',
        onError: handleError,
      );

      final PageRoute<dynamic> route = MockPageRoute();
      final PageRoute<dynamic> previousRoute = MockPageRoute();

      final PlatformException thrownException = PlatformException(code: 'b');
      Future<void> throwPlatformException() async => throw thrownException;

      when(analytics.setCurrentScreen(screenName: anyNamed('screenName')))
          .thenAnswer((Invocation invocation) => throwPlatformException());

      observer.didPush(route, previousRoute);

      await pumpEventQueue();
      expect(passedException, thrownException);
    });
  });
}

class MockFirebaseAnalytics extends Mock implements FirebaseAnalytics {
  @override
  Future<void> setCurrentScreen(
      {required String? screenName, String? screenClassOverride = 'Flutter'}) {
    return super.noSuchMethod(
      Invocation.method(#setCurrentScreen, [],
          {#screenName: screenName, #screenClassOverride: screenClassOverride}),
      returnValue: Future.value(),
      returnValueForMissingStub: Future.value(),
    );
  }
}

class MockPageRoute extends Mock implements PageRoute<dynamic> {
  @override
  RouteSettings get settings => super.noSuchMethod(
        Invocation.getter(#settings),
        returnValue: const RouteSettings(),
        returnValueForMissingStub: const RouteSettings(),
      );
}
