From 3e9734133d9525140368e28f14e308f36e52dbc7 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 27 Jan 2021 14:11:58 +0100 Subject: [PATCH 1/2] add streamchatcore tests --- .../example/ios/Flutter/Debug.xcconfig | 1 + .../example/ios/Flutter/Release.xcconfig | 1 + .../lib/src/stream_chat_core.dart | 1 + .../stream_chat_flutter_core/pubspec.yaml | 39 +--- .../test/stream_chat_core_test.dart | 189 ++++++++++++++++++ .../test/stream_chat_flutter_core_test.dart | 92 --------- 6 files changed, 194 insertions(+), 129 deletions(-) create mode 100644 packages/stream_chat_flutter_core/test/stream_chat_core_test.dart delete mode 100644 packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart diff --git a/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig b/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig index 592ceee8..e8efba11 100644 --- a/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig +++ b/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig b/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig index 592ceee8..399e9340 100644 --- a/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig +++ b/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index b04542fc..73f5eb61 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -142,6 +142,7 @@ class StreamChatCoreState extends State @override void dispose() { WidgetsBinding.instance.removeObserver(this); + _disconnectTimer?.cancel(); super.dispose(); } } diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index c0c2a55a..5037e5e8 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -19,40 +19,5 @@ dev_dependencies: mockito: ^4.1.4 flutter_test: sdk: flutter - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages + fake_async: ^1.1.0 + \ No newline at end of file diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart new file mode 100644 index 00000000..714375de --- /dev/null +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -0,0 +1,189 @@ +import 'dart:async'; + +import 'package:fake_async/fake_async.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:mockito/mockito.dart'; + +import 'mocks.dart'; + +class MockShowLocalNotifications extends Mock { + void call(Message m, ChannelModel cm); +} + +void main() { + testWidgets( + 'StreamChatCore.of(context) should throw an exception', + (WidgetTester tester) async { + await tester.pumpWidget( + Builder( + builder: (context) { + expect(() => StreamChatCore.of(context), throwsException); + return Container(); + }, + ), + ); + }, + ); + + testWidgets( + 'StreamChatCore.of(context) should return the StreamChatCore ancestor', + (WidgetTester tester) async { + final client = MockClient(); + await tester.pumpWidget( + StreamChatCore( + client: client, + child: Builder( + builder: (context) { + final sc = StreamChatCore.of(context); + expect(sc, isNotNull); + return Container(); + }, + ), + ), + ); + }, + ); + + testWidgets( + 'StreamChatCore.of(context).client should return the client', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn( + OwnUser( + id: 'test', + ), + ); + final userStream = Stream.value( + OwnUser( + id: 'test', + ), + ); + when(clientState.userStream).thenAnswer( + (_) => userStream, + ); + + await tester.pumpWidget( + StreamChatCore( + client: client, + child: Builder( + builder: (context) { + final sc = StreamChatCore.of(context); + expect(sc.client, client); + expect(sc.user, client.state.user); + expect(sc.userStream, userStream); + return Container(); + }, + ), + ), + ); + }, + ); + + testWidgets( + 'StreamChatCore should disconnect on background', + (WidgetTester tester) async { + await fakeAsync((_async) { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn( + OwnUser( + id: 'test', + ), + ); + final showLocalNotificationMock = MockShowLocalNotifications().call; + when(client.showLocalNotification) + .thenReturn(showLocalNotificationMock); + when(client.backgroundKeepAlive).thenReturn(Duration( + seconds: 4, + )); + final eventStreamController = StreamController(); + when(client.on(EventType.messageNew)) + .thenAnswer((_) => eventStreamController.stream); + + when(client.channel('test', id: 'testid')).thenReturn(channel); + + final scKey = GlobalKey(); + tester.pumpWidget( + StreamChatCore( + key: scKey, + client: client, + child: Builder( + builder: (context) { + return Container(); + }, + ), + ), + ); + + final sc = scKey.currentState; + sc.didChangeAppLifecycleState(AppLifecycleState.paused); + + _async.elapse(Duration(seconds: 5)); + + verify(client.disconnect()).called(1); + }); + }, + ); + + testWidgets( + 'StreamChatCore should handle notifications when on background and connected', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn( + OwnUser( + id: 'test', + ), + ); + final showLocalNotificationMock = MockShowLocalNotifications().call; + when(client.showLocalNotification).thenReturn(showLocalNotificationMock); + when(client.backgroundKeepAlive).thenReturn(Duration( + seconds: 4, + )); + final eventStreamController = StreamController(); + when(client.on(EventType.messageNew)) + .thenAnswer((_) => eventStreamController.stream); + + when(client.channel('test', id: 'testid')).thenReturn(channel); + + final scKey = GlobalKey(); + await tester.pumpWidget( + StreamChatCore( + key: scKey, + client: client, + child: Builder( + builder: (context) { + return Container(); + }, + ), + ), + ); + + final sc = scKey.currentState; + sc.didChangeAppLifecycleState(AppLifecycleState.paused); + final event = Event( + type: EventType.messageNew, + message: Message(text: 'hey'), + channelType: 'test', + channelId: 'testid', + user: User(id: 'other user'), + ); + eventStreamController.add(event); + + await untilCalled(showLocalNotificationMock(any, any)); + + verify(showLocalNotificationMock( + event.message, + any, + )).called(1); + }, + ); +} diff --git a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart deleted file mode 100644 index be619db5..00000000 --- a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; -import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - -import 'mocks.dart'; - -void main() { - testWidgets( - 'it should show basic channel information', - (WidgetTester tester) async { - // final client = MockClient(); - // final clientState = MockClientState(); - // final channel = MockChannel(); - // final channelState = MockChannelState(); - // final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - // - // when(client.state).thenReturn(clientState); - // when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - // when(channel.lastMessageAt).thenReturn(lastMessageAt); - // when(channel.state).thenReturn(channelState); - // when(channel.client).thenReturn(client); - // when(channel.isMuted).thenReturn(false); - // when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - // when(channel.extraDataStream).thenAnswer((i) => Stream.value({ - // 'name': 'test name', - // })); - // when(channel.extraData).thenReturn({ - // 'name': 'test name', - // }); - // when(channelState.unreadCount).thenReturn(1); - // when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - // when(channelState.membersStream).thenAnswer((i) => Stream.value([ - // Member( - // userId: 'user-id', - // user: User(id: 'user-id'), - // ) - // ])); - // when(channelState.members).thenReturn([ - // Member( - // userId: 'user-id', - // user: User(id: 'user-id'), - // ), - // ]); - // when(channelState.messages).thenReturn([ - // Message( - // text: 'hello', - // user: User(id: 'other-user'), - // ) - // ]); - // when(channelState.messagesStream).thenAnswer((i) => Stream.value([ - // Message( - // text: 'hello', - // user: User(id: 'other-user'), - // ) - // ])); - - await tester.pumpWidget( - // MaterialApp( - // home: StreamChatCore( - // client: client, - // child: StreamChannel( - // channel: channel, - // child: Scaffold( - // body: MessageListCore( - // loadingBuilder: (context) { - // return Center(); - // }, - // emptyBuilder: (context) { - // return Center(); - // }, - // messageListBuilder: (context, list) { - // return ListView.builder( - // itemBuilder: (context, position) { - // return Text(list[position].text); - // }, - // itemCount: list.length, - // ); - // }, - // ), - // ), - // ), - // ), - // ), - MaterialApp(home: Scaffold(body: Text('hello'))), - ); - - expect(find.text('hello'), findsOneWidget); - }, - ); -} From cb90146a8b78b9626889a1d431f4eb05f6194850 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 28 Jan 2021 10:25:52 +0100 Subject: [PATCH 2/2] fix examples --- .gitignore | 1 + .../example/android/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.properties | 2 +- packages/dart_client/example/lib/main.dart | 2 +- .../example/android/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../example/ios/Flutter/Debug.xcconfig | 1 + .../example/ios/Flutter/Release.xcconfig | 1 + .../example/lib/main.dart | 103 +++++++++--------- 9 files changed, 62 insertions(+), 52 deletions(-) diff --git a/.gitignore b/.gitignore index 6d180702..f7a23bbb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store .atom/ .idea/ +.vscode/ .packages .pub/ diff --git a/packages/dart_client/example/android/gradle.properties b/packages/dart_client/example/android/gradle.properties index 94adc3a3..a6738207 100644 --- a/packages/dart_client/example/android/gradle.properties +++ b/packages/dart_client/example/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true +android.enableR8=true diff --git a/packages/dart_client/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/dart_client/example/android/gradle/wrapper/gradle-wrapper.properties index 296b146b..de2ccd60 100644 --- a/packages/dart_client/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/dart_client/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/packages/dart_client/example/lib/main.dart b/packages/dart_client/example/lib/main.dart index 8f68c100..ed21df76 100644 --- a/packages/dart_client/example/lib/main.dart +++ b/packages/dart_client/example/lib/main.dart @@ -156,7 +156,7 @@ class _MessageViewState extends State { /// Convenience method for scrolling the list view when a new message is sent. void _updateList() { _scrollController.animateTo( - _scrollController.position.maxScrollExtent, + 0, duration: const Duration(milliseconds: 200), curve: Curves.easeOut, ); diff --git a/packages/stream_chat_flutter_core/example/android/gradle.properties b/packages/stream_chat_flutter_core/example/android/gradle.properties index 94adc3a3..a6738207 100644 --- a/packages/stream_chat_flutter_core/example/android/gradle.properties +++ b/packages/stream_chat_flutter_core/example/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true +android.enableR8=true diff --git a/packages/stream_chat_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/stream_chat_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties index 296b146b..de2ccd60 100644 --- a/packages/stream_chat_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/stream_chat_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig b/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig index 592ceee8..e8efba11 100644 --- a/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig +++ b/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig b/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig index 592ceee8..399e9340 100644 --- a/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig +++ b/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index a4237148..19945dff 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -55,7 +55,7 @@ class StreamExample extends StatelessWidget { home: HomeScreen(), builder: (context, child) => StreamChatCore( client: client, - child: ChannelsBloc(child: child), + child: child, ), ); } @@ -73,53 +73,58 @@ class HomeScreen extends StatelessWidget { appBar: AppBar( title: Text('Channels'), ), - body: ChannelListCore( - emptyBuilder: (BuildContext context) { - return Center( - child: Text('Looks like you are not in any channels'), - ); - }, - loadingBuilder: (BuildContext context) { - return Center( - child: SizedBox( - height: 100.0, - width: 100.0, - child: CircularProgressIndicator(), - ), - ); - }, - errorBuilder: (Error error) { - return Center( - child: - Text('Oh no, something went wrong. Please check your config.'), - ); - }, - listBuilder: (BuildContext context, List channels) => - ListView.builder( - itemCount: channels.length, - itemBuilder: (BuildContext context, int index) { - final _item = channels[index]; - return ListTile( - title: Text(_item.name), - subtitle: Text(_item.state.lastMessage.text), - onTap: () { - /// Display a list of messages when the user taps on an item. - /// We can use [StreamChannel] to wrap our [MessageScreen] screen - /// with the selected channel. - /// - /// This allows us to use a built-in inherited widget for accessing - /// our `channel` later on. - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: _item, - child: MessageScreen(), - ), - ), - ); - }, + body: ChannelsBloc( + child: ChannelListCore( + emptyBuilder: (BuildContext context) { + return Center( + child: Text('Looks like you are not in any channels'), ); }, + loadingBuilder: (BuildContext context) { + return Center( + child: SizedBox( + height: 100.0, + width: 100.0, + child: CircularProgressIndicator(), + ), + ); + }, + errorBuilder: (Error error) { + return Center( + child: Text( + 'Oh no, something went wrong. Please check your config.'), + ); + }, + listBuilder: ( + BuildContext context, + List channels, + ) => + ListView.builder( + itemCount: channels.length, + itemBuilder: (BuildContext context, int index) { + final _item = channels[index]; + return ListTile( + title: Text(_item.name), + subtitle: Text(_item.state.lastMessage.text), + onTap: () { + /// Display a list of messages when the user taps on an item. + /// We can use [StreamChannel] to wrap our [MessageScreen] screen + /// with the selected channel. + /// + /// This allows us to use a built-in inherited widget for accessing + /// our `channel` later on. + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: _item, + child: MessageScreen(), + ), + ), + ); + }, + ); + }, + ), ), ), ); @@ -157,7 +162,7 @@ class _MessageScreenState extends State { void _updateList() { _scrollController.animateTo( - _scrollController.position.maxScrollExtent, + 0, duration: const Duration(milliseconds: 200), curve: Curves.easeOut, ); @@ -179,7 +184,7 @@ class _MessageScreenState extends State { child: MessageListCore( emptyBuilder: (BuildContext context) { return Center( - child: Text('Looks like you are not in any channels'), + child: Text('Nothing here yet'), ); }, loadingBuilder: (BuildContext context) { @@ -288,4 +293,4 @@ extension on Channel { return cid; } } -} \ No newline at end of file +}