diff --git a/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_widgets_test.dart b/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_widgets_test.dart index e022e0bd8..b616abc4d 100644 --- a/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_widgets_test.dart +++ b/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_widgets_test.dart @@ -1,10 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:provider/provider.dart'; +import 'package:ubuntupro/core/agent_api_client.dart'; import 'package:ubuntupro/core/pro_token.dart'; +import 'package:ubuntupro/pages/subscribe_now/subscribe_now_model.dart'; import 'package:ubuntupro/pages/subscribe_now/subscribe_now_widgets.dart'; +import '../../utils/build_multiprovider_app.dart'; import '../../utils/token_samples.dart' as tks; +import 'subscribe_now_widgets_test.mocks.dart'; +@GenerateMocks([AgentApiClient]) void main() { group('pro token value', () { test('errors', () async { @@ -47,7 +54,7 @@ void main() { group('pro token input', () { group('basic flow', () { - final theApp = buildApp(onApply: (_) {}, isExpanded: true); + final theApp = buildApp(onApply: () {}, isExpanded: true); testWidgets('starts with no error', (tester) async { await tester.pumpWidget(theApp); @@ -55,14 +62,6 @@ void main() { expect(input.decoration!.errorText, isNull); }); - testWidgets('starts with button disabled', (tester) async { - await tester.pumpWidget(theApp); - - final button = - tester.firstWidget(find.byType(ElevatedButton)); - - expect(button.enabled, isFalse); - }); testWidgets('invalid non-empty tokens', (tester) async { await tester.pumpWidget(theApp); @@ -79,10 +78,6 @@ void main() { matching: find.text(lang.tokenErrorInvalid), ); expect(errorText, findsOne); - - final button = - tester.firstWidget(find.byType(ElevatedButton)); - expect(button.enabled, isFalse); } }); @@ -102,16 +97,11 @@ void main() { ); expect(errorText, findsOne); - final button = - tester.firstWidget(find.byType(ElevatedButton)); - expect(button.enabled, isFalse); - // ...except when we delete the content we should have no more errors await tester.enterText(inputField, ''); await tester.pump(); final input = tester.firstWidget(inputField); expect(input.decoration!.error, isNull); - expect(button.enabled, isFalse); }); testWidgets('good token', (tester) async { @@ -125,19 +115,18 @@ void main() { final input = tester.firstWidget(inputField); expect(input.decoration!.error, isNull); - final validText = find.descendant( + final errorText = find.descendant( of: inputField, - matching: find.text(lang.tokenValid), + matching: find.text(lang.tokenErrorInvalid), ); - expect(validText, findsOne); - - final button = - tester.firstWidget(find.byType(ElevatedButton)); - expect(button.enabled, isTrue); + expect(errorText, findsNothing); }); + testWidgets('good token with spaces', (tester) async { await tester.pumpWidget(theApp); final inputField = find.byType(TextField); + final context = tester.element(inputField); + final lang = AppLocalizations.of(context); await tester.enterText( inputField, @@ -149,14 +138,17 @@ void main() { final input = tester.firstWidget(inputField); expect(input.decoration!.errorText, isNull); - final button = - tester.firstWidget(find.byType(ElevatedButton)); - expect(button.enabled, isTrue); + final errorText = find.descendant( + of: inputField, + matching: find.text(lang.tokenErrorInvalid), + ); + expect(errorText, findsNothing); }); }); + testWidgets('apply', (tester) async { var called = false; - final theApp = buildApp(isExpanded: true, onApply: (_) => called = true); + final theApp = buildApp(isExpanded: true, onApply: () => called = true); await tester.pumpWidget(theApp); final inputField = find.byType(TextField); @@ -165,15 +157,15 @@ void main() { await tester.pump(); expect(called, isFalse); - final button = find.byType(ElevatedButton); - await tester.tap(button); + // simulate an enter key/submission of the text field + await tester.testTextInput.receiveAction(TextInputAction.done); await tester.pumpAndSettle(); expect(called, isTrue); }); testWidgets('apply on submit', (tester) async { var called = false; - final theApp = buildApp(isExpanded: true, onApply: (_) => called = true); + final theApp = buildApp(isExpanded: true, onApply: () => called = true); await tester.pumpWidget(theApp); final textFieldFinder = find.byType(TextField); @@ -187,7 +179,7 @@ void main() { }); testWidgets('token error enum l10n', (tester) async { - final theApp = buildApp(isExpanded: true, onApply: (_) {}); + final theApp = buildApp(isExpanded: true, onApply: () {}); await tester.pumpWidget(theApp); final context = tester.element(find.byType(ProTokenInputField)); final lang = AppLocalizations.of(context); @@ -198,17 +190,21 @@ void main() { }); } -MaterialApp buildApp({ - required void Function(ProToken) onApply, +Widget buildApp({ + required void Function() onApply, bool isExpanded = false, }) { - return MaterialApp( - home: Scaffold( + return buildSingleRouteMultiProviderApp( + child: Scaffold( body: ProTokenInputField( - onApply: onApply, + onSubmit: onApply, isExpanded: isExpanded, ), ), - localizationsDelegates: AppLocalizations.localizationsDelegates, + providers: [ + ChangeNotifierProvider.value( + value: SubscribeNowModel(MockAgentApiClient()), + ), + ], ); } diff --git a/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_widgets_test.mocks.dart b/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_widgets_test.mocks.dart new file mode 100644 index 000000000..4fd4aede4 --- /dev/null +++ b/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_widgets_test.mocks.dart @@ -0,0 +1,185 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in ubuntupro/test/pages/subcribe_now/subscribe_now_widgets_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i5; +import 'dart:io' as _i6; + +import 'package:agentapi/agentapi.dart' as _i2; +import 'package:grpc/grpc.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; +import 'package:ubuntupro/core/agent_api_client.dart' as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeUIClient_0 extends _i1.SmartFake implements _i2.UIClient { + _FakeUIClient_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSubscriptionInfo_1 extends _i1.SmartFake + implements _i2.SubscriptionInfo { + _FakeSubscriptionInfo_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeLandscapeSource_2 extends _i1.SmartFake + implements _i2.LandscapeSource { + _FakeLandscapeSource_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeConfigSources_3 extends _i1.SmartFake implements _i2.ConfigSources { + _FakeConfigSources_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [AgentApiClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAgentApiClient extends _i1.Mock implements _i3.AgentApiClient { + MockAgentApiClient() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.UIClient Function(_i4.ClientChannel) get stubFactory => + (super.noSuchMethod( + Invocation.getter(#stubFactory), + returnValue: (_i4.ClientChannel __p0) => _FakeUIClient_0( + this, + Invocation.getter(#stubFactory), + ), + ) as _i2.UIClient Function(_i4.ClientChannel)); + + @override + _i5.Stream<_i3.ConnectionEvent> get onConnectionChanged => + (super.noSuchMethod( + Invocation.getter(#onConnectionChanged), + returnValue: _i5.Stream<_i3.ConnectionEvent>.empty(), + ) as _i5.Stream<_i3.ConnectionEvent>); + + @override + _i5.Future connectTo( + String? host, + int? port, + _i6.Directory? certsDir, + ) => + (super.noSuchMethod( + Invocation.method( + #connectTo, + [ + host, + port, + certsDir, + ], + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + + @override + _i5.Future<_i2.SubscriptionInfo> applyProToken(String? token) => + (super.noSuchMethod( + Invocation.method( + #applyProToken, + [token], + ), + returnValue: + _i5.Future<_i2.SubscriptionInfo>.value(_FakeSubscriptionInfo_1( + this, + Invocation.method( + #applyProToken, + [token], + ), + )), + ) as _i5.Future<_i2.SubscriptionInfo>); + + @override + _i5.Future<_i2.LandscapeSource> applyLandscapeConfig(String? config) => + (super.noSuchMethod( + Invocation.method( + #applyLandscapeConfig, + [config], + ), + returnValue: + _i5.Future<_i2.LandscapeSource>.value(_FakeLandscapeSource_2( + this, + Invocation.method( + #applyLandscapeConfig, + [config], + ), + )), + ) as _i5.Future<_i2.LandscapeSource>); + + @override + _i5.Future ping() => (super.noSuchMethod( + Invocation.method( + #ping, + [], + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + + @override + _i5.Future<_i2.ConfigSources> configSources() => (super.noSuchMethod( + Invocation.method( + #configSources, + [], + ), + returnValue: _i5.Future<_i2.ConfigSources>.value(_FakeConfigSources_3( + this, + Invocation.method( + #configSources, + [], + ), + )), + ) as _i5.Future<_i2.ConfigSources>); + + @override + _i5.Future<_i2.SubscriptionInfo> notifyPurchase() => (super.noSuchMethod( + Invocation.method( + #notifyPurchase, + [], + ), + returnValue: + _i5.Future<_i2.SubscriptionInfo>.value(_FakeSubscriptionInfo_1( + this, + Invocation.method( + #notifyPurchase, + [], + ), + )), + ) as _i5.Future<_i2.SubscriptionInfo>); +}