diff --git a/lib/main.dart b/lib/main.dart index 7501b63..4e68804 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,14 +15,16 @@ void main() { runApp(const App()); } +const testnetNetwork = Network( + name: NetworkName.testnet, + walletProxyConfig: WalletProxyConfig( + baseUrl: 'https://wallet-proxy.testnet.concordium.com', + ), +); + // In the future, this will be loaded from a proper source rather than being hardcoded. final config = Config.ofNetworks([ - const Network( - name: NetworkName.testnet, - walletProxyConfig: WalletProxyConfig( - baseUrl: 'https://wallet-proxy.testnet.concordium.com', - ), - ), + testnetNetwork, ]); class App extends StatelessWidget { diff --git a/lib/screens/home/screen.dart b/lib/screens/home/screen.dart index cf86f80..f080a5a 100644 --- a/lib/screens/home/screen.dart +++ b/lib/screens/home/screen.dart @@ -1,4 +1,5 @@ import 'package:concordium_wallet/screens/terms_and_conditions/screen.dart'; +import 'package:concordium_wallet/services/url_launcher.dart'; import 'package:concordium_wallet/services/wallet_proxy/service.dart'; import 'package:concordium_wallet/state/network.dart'; import 'package:concordium_wallet/state/services.dart'; @@ -67,6 +68,7 @@ class _HomeScreenState extends State { return TermsAndConditionsScreen( validTermsAndConditions: validTac.termsAndConditions, acceptedTermsAndConditionsVersion: acceptedTac?.version, + urlLauncher: UrlLauncher(), ); } return Column( diff --git a/lib/screens/terms_and_conditions/screen.dart b/lib/screens/terms_and_conditions/screen.dart index 721a737..1b4367a 100644 --- a/lib/screens/terms_and_conditions/screen.dart +++ b/lib/screens/terms_and_conditions/screen.dart @@ -11,7 +11,8 @@ class TermsAndConditionsScreen extends StatefulWidget { final String? acceptedTermsAndConditionsVersion; final UrlLauncher urlLauncher; - const TermsAndConditionsScreen({super.key, required this.validTermsAndConditions, this.acceptedTermsAndConditionsVersion, required this.urlLauncher}); + const TermsAndConditionsScreen( + {super.key, required this.validTermsAndConditions, this.acceptedTermsAndConditionsVersion, required this.urlLauncher}); @override State createState() => _TermsAndConditionsScreenState(); @@ -76,7 +77,6 @@ class _TermsAndConditionsScreenState extends State { children: [ Flexible( child: GestureDetector( - key: const Key("TermsAndConditionsText"), onTap: () { _launchUrl(widget.validTermsAndConditions.url); }, @@ -112,7 +112,6 @@ class _TermsAndConditionsScreenState extends State { ), const SizedBox(height: 9), ElevatedButton( - key: const Key("Continue"), onPressed: _onAcceptButtonPressed(context), child: const Text('Continue'), ), diff --git a/lib/services/wallet_proxy/service.dart b/lib/services/wallet_proxy/service.dart index 60d9e65..edd9395 100644 --- a/lib/services/wallet_proxy/service.dart +++ b/lib/services/wallet_proxy/service.dart @@ -8,7 +8,6 @@ enum WalletProxyEndpoint { termsAndConditionsVersion('v0/termsAndConditionsVersion'); final String path; - const WalletProxyEndpoint(this.path); } diff --git a/pubspec.lock b/pubspec.lock index 7bf9b42..03a8e77 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.2" + bloc_test: + dependency: "direct dev" + description: + name: bloc_test + sha256: "02f04270be5abae8df171143e61a0058a7acbce5dcac887612e89bb40cca4c33" + url: "https://pub.dev" + source: hosted + version: "9.1.5" boolean_selector: dependency: transitive description: @@ -161,6 +169,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "595a29b55ce82d53398e1bcc2cba525d7bd7c59faeb2d2540e9d42c390cfeeeb" + url: "https://pub.dev" + source: hosted + version: "1.6.4" crypto: dependency: transitive description: @@ -185,6 +201,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" + url: "https://pub.dev" + source: hosted + version: "0.4.1" fake_async: dependency: transitive description: @@ -400,6 +424,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" package_config: dependency: transitive description: @@ -568,6 +600,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" shelf_web_socket: dependency: transitive description: @@ -597,6 +645,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.4" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" source_span: dependency: transitive description: @@ -645,6 +709,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + test: + dependency: transitive + description: + name: test + sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" + url: "https://pub.dev" + source: hosted + version: "1.24.3" test_api: dependency: transitive description: @@ -653,6 +725,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + test_core: + dependency: transitive + description: + name: test_core + sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" + url: "https://pub.dev" + source: hosted + version: "0.5.3" timing: dependency: transitive description: @@ -765,6 +845,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 + url: "https://pub.dev" + source: hosted + version: "11.10.0" watcher: dependency: transitive description: @@ -789,6 +877,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d8a4dc8..dffe9e2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,7 @@ dev_dependencies: flutter_lints: ^2.0.0 json_serializable: ^6.7.1 mocktail: ^1.0.1 + bloc_test: ^9.1.5 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/test/helpers.dart b/test/helpers.dart new file mode 100644 index 0000000..e52789f --- /dev/null +++ b/test/helpers.dart @@ -0,0 +1,4 @@ +import 'package:flutter/material.dart'; + +/// Wrap MaterialApp and Scaffold around the given widget. +Widget wrapMaterial({required Widget? child}) => MaterialApp(home: Scaffold(body: child)); diff --git a/test/terms_and_conditions_test.dart b/test/terms_and_conditions_test.dart index f133e9b..f30bc45 100644 --- a/test/terms_and_conditions_test.dart +++ b/test/terms_and_conditions_test.dart @@ -1,93 +1,104 @@ +import 'package:bloc_test/bloc_test.dart'; import 'package:concordium_wallet/services/url_launcher.dart'; -import 'package:concordium_wallet/state.dart'; +import 'package:concordium_wallet/state/terms_and_conditions.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mocktail/mocktail.dart'; import 'package:concordium_wallet/screens/terms_and_conditions/widget.dart'; import 'package:concordium_wallet/screens/terms_and_conditions/screen.dart'; import 'package:concordium_wallet/services/wallet_proxy/model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:provider/provider.dart'; -class MockAppSharedPreferences extends Mock implements AppSharedPreferences {} - -class MockAppState extends Mock implements AppState { - @override - AppSharedPreferences get sharedPreferences => MockAppSharedPreferences(); -} +import 'helpers.dart'; class MockUrlLauncher extends Mock implements UrlLauncher {} - -/// Wrap MaterialApp and a Mocked App State around the given widget. -Widget makeTestableWidget({required Widget? child}) => MaterialApp( - home: ChangeNotifierProvider( - create: (_) => MockAppState(), - child: Scaffold( - body: child, - ), - ), - ); +class MockTACCubit extends MockCubit implements TermsAndConditionAcceptance {} void main() { group("Terms and conditions screen", () { bool checked = false; - TermsAndConditionsScreen? tacScreen; + late Widget tacScreen; + late MockTACCubit mockTACCubit; + late TermsAndConditionsAcceptanceState state; + const String validVersion = "1.1.0"; + const String acceptedVersion = "1.0.0"; + + setUpAll(() { + registerFallbackValue(const AcceptedTermsAndConditions(version: validVersion)); + }); setUp(() { checked = false; + + final terms = TermsAndConditions(Uri.parse("localhost"), validVersion); + state = TermsAndConditionsAcceptanceState( + accepted: const AcceptedTermsAndConditions(version: acceptedVersion), + valid: ValidTermsAndConditions.refreshedNow(termsAndConditions: terms)); + // Build the terms and condition screen we wish to test - tacScreen = TermsAndConditionsScreen( - TermsAndConditionsViewModel( - TermsAndConditions(Uri.parse("localhost"), "1.1.0"), - "1.0.0", - (context) => checked = true, - ), - MockUrlLauncher()); + final rawTacScreen = TermsAndConditionsScreen(validTermsAndConditions: terms, urlLauncher: MockUrlLauncher()); + mockTACCubit = MockTACCubit(); + + tacScreen = BlocProvider.value(value: mockTACCubit, child: wrapMaterial(child: rawTacScreen)); + + when(() => mockTACCubit.state).thenAnswer((_) => state); + when(() => mockTACCubit.userAccepted(any())).thenAnswer((_) { + checked = true; + }); }); testWidgets('Pressing continue does not perform check', (WidgetTester tester) async { - await tester.pumpWidget(makeTestableWidget(child: tacScreen)); + // Arrange + await tester.pumpWidget(wrapMaterial(child: tacScreen)); - // TODO add better way to get widget (Not rely on matching strings) - await tester.tap(find.byKey(const Key("Continue"))); + // Act + // TODO use internationalized version here. + await tester.tap(find.text("Continue", findRichText: true)); + // Assert expect(checked, false); }); testWidgets('Pressing continue, after toggling accept, performs check', (WidgetTester tester) async { - await tester.pumpWidget(makeTestableWidget(child: tacScreen)); + // Arrange + await tester.pumpWidget(tacScreen); + // Act await tester.tap(find.byType(ToggleAcceptedWidget)); await tester.pump(); - await tester.tap(find.byKey(const Key("Continue"))); + // TODO use internationalized version here. + await tester.tap(find.text("Continue", findRichText: true)); await tester.pump(); + // Assert expect(checked, true); }); }); testWidgets('Clicking on terms and conditions', (WidgetTester tester) async { + // Arrange Uri uri = Uri.parse("localhost"); var launcher = MockUrlLauncher(); // Build the terms and condition screen we wish to test var tacScreen = TermsAndConditionsScreen( - TermsAndConditionsViewModel( - TermsAndConditions(uri, "1.1.0"), - "1.0.0", - (context) {}, - ), - launcher); + validTermsAndConditions: TermsAndConditions(uri, "1.1.0"), + urlLauncher: launcher, + ); - await tester.pumpWidget(makeTestableWidget(child: tacScreen)); + await tester.pumpWidget(wrapMaterial(child: tacScreen)); when(() => launcher.canLaunch(uri)).thenAnswer((_) => Future.value(true)); when(() => launcher.launch(uri)).thenAnswer((_) => Future.value(true)); - await tester.tap(find.byKey(const Key("TermsAndConditionsText"))); + // Act + // TODO use internationalized version here. + await tester.tap(find.textContaining("I have read and agree to the", findRichText: true)); + // Assert verify(() => launcher.launch(uri)).called(1); }); } diff --git a/test/wallet_proxy_service_test.dart b/test/wallet_proxy_service_test.dart index 87f1aa1..09f3f55 100644 --- a/test/wallet_proxy_service_test.dart +++ b/test/wallet_proxy_service_test.dart @@ -1,6 +1,6 @@ +import 'package:concordium_wallet/main.dart'; import 'package:concordium_wallet/services/http.dart'; import 'package:concordium_wallet/services/wallet_proxy/service.dart'; -import 'package:concordium_wallet/state.dart'; import 'package:mocktail/mocktail.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; @@ -25,10 +25,10 @@ void main() { var httpClient = MockHttpService(rawData); - var service = WalletProxyService(config: WalletProxyConfig(baseUrl: 'http://test.com'), httpService: httpClient); + var service = WalletProxyService(config: const WalletProxyConfig(baseUrl: 'http://test.com'), httpService: httpClient); // Act - var tac = await service.getTac(); + var tac = await service.fetchTermsAndConditions(); // Assert expect(tac.url.toString(), tacUrl); @@ -36,6 +36,7 @@ void main() { }); test('WalletProxyConfig for testnet merges base and path correctly for terms and conditions', () { - expect(testnet.walletProxyConfig.urlOf(WalletProxyEndpoint.tacVersion).toString(), 'https://wallet-proxy.testnet.concordium.com/v0/termsAndConditionsVersion'); + expect(testnetNetwork.walletProxyConfig.urlOf(WalletProxyEndpoint.termsAndConditionsVersion).toString(), + 'https://wallet-proxy.testnet.concordium.com/v0/termsAndConditionsVersion'); }); }