diff --git a/scripts/accounts/gcloud/setup-firebase.sh b/scripts/accounts/gcloud/setup-firebase.sh old mode 100755 new mode 100644 diff --git a/scripts/pipelines/common/templates/package/flutter-package.sh b/scripts/pipelines/common/templates/package/flutter-package.sh old mode 100755 new mode 100644 diff --git a/scripts/pipelines/gcloud/pipeline_generator.sh b/scripts/pipelines/gcloud/pipeline_generator.sh old mode 100755 new mode 100644 diff --git a/scripts/pipelines/gcloud/templates/common/secret/get-gcloud-secret-vars.sh b/scripts/pipelines/gcloud/templates/common/secret/get-gcloud-secret-vars.sh old mode 100755 new mode 100644 diff --git a/takeoff/takeoff_gui/generate_coverage.ps1 b/takeoff/takeoff_gui/generate_coverage.ps1 new file mode 100644 index 000000000..26f0f3c28 --- /dev/null +++ b/takeoff/takeoff_gui/generate_coverage.ps1 @@ -0,0 +1,8 @@ +flutter clean +flutter pub get +flutter pub run build_runner build --delete-conflicting-outputs +flutter test --coverage +remove_from_coverage -f coverage/lcov.info -r '.g.dart$' +remove_from_coverage -f coverage/lcov.info -r '_libw.dart$' +remove_from_coverage -f coverage/lcov.info -r 'options.dart$' +remove_from_coverage -f coverage/lcov.info -r 'mock_projects.dart$' diff --git a/takeoff/takeoff_gui/lib/common/custom_button.dart b/takeoff/takeoff_gui/lib/common/custom_button.dart index f9d035052..8686a4605 100644 --- a/takeoff/takeoff_gui/lib/common/custom_button.dart +++ b/takeoff/takeoff_gui/lib/common/custom_button.dart @@ -15,21 +15,15 @@ class CustomButton extends StatelessWidget { @override Widget build(BuildContext context) { - return ElevatedButton( + return ElevatedButton.icon( onPressed: onPressed == null ? null : () => onPressed!(), style: ElevatedButton.styleFrom( backgroundColor: color, minimumSize: const Size(150, 50), maximumSize: const Size(170, 50), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon), - const SizedBox(width: 10), - Text(text), - ], - ), + icon: Icon(icon), + label: Text(text), ); } } diff --git a/takeoff/takeoff_gui/lib/common/monitor/pages/monitor_dialog.dart b/takeoff/takeoff_gui/lib/common/monitor/pages/monitor_dialog.dart index 2703d9414..15e05e1b1 100644 --- a/takeoff/takeoff_gui/lib/common/monitor/pages/monitor_dialog.dart +++ b/takeoff/takeoff_gui/lib/common/monitor/pages/monitor_dialog.dart @@ -30,20 +30,22 @@ class MonitorDialog extends StatelessWidget { child: Column( children: [ Observer( - builder: (_) => Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - controller.hasFinished - ? AppLocalizations.of(context)! - .projectCreationFinishedMessage - : AppLocalizations.of(context)! - .creatingProjectMessage, - style: const TextStyle(fontSize: 30)), - if (!controller.hasFinished) - const CircularProgressIndicator(), - ], - ), + builder: (_) => SizedBox( + //mainAxisAlignment: MainAxisAlignment.spaceEvenly, + child: controller.hasFinished + ? Text( + AppLocalizations.of(context)! + .projectCreationFinishedMessage, + style: const TextStyle(fontSize: 30)) + : Row( + children: [ + Text( + AppLocalizations.of(context)! + .creatingProjectMessage, + style: const TextStyle(fontSize: 30)), + const CircularProgressIndicator(), + ], + )), ), const SizedBox(height: 50), SizedBox( @@ -98,6 +100,7 @@ class MonitorDialog extends StatelessWidget { }); } + @visibleForTesting _generateDialog(GuiMessage message, BuildContext context) { switch (message.type) { case MessageType.info: diff --git a/takeoff/takeoff_gui/lib/common/monitor/pages/user_interaction_dialog.dart b/takeoff/takeoff_gui/lib/common/monitor/pages/user_interaction_dialog.dart index b2d9f92e0..6343b9878 100644 --- a/takeoff/takeoff_gui/lib/common/monitor/pages/user_interaction_dialog.dart +++ b/takeoff/takeoff_gui/lib/common/monitor/pages/user_interaction_dialog.dart @@ -35,15 +35,11 @@ class _UserInteractionDialogState extends State { padding: const EdgeInsets.symmetric(horizontal: 30.0), child: SingleChildScrollView( child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - AppLocalizations.of(context)!.followStepsMessage, - style: const TextStyle(fontSize: 30), - ), - ], + Text( + AppLocalizations.of(context)!.followStepsMessage, + style: const TextStyle(fontSize: 30), ), const SizedBox(height: 50), SelectableText( diff --git a/takeoff/takeoff_gui/lib/features/create/controllers/create_controller.dart b/takeoff/takeoff_gui/lib/features/create/controllers/create_controller.dart index 4b368dff9..80a5bbd58 100644 --- a/takeoff/takeoff_gui/lib/features/create/controllers/create_controller.dart +++ b/takeoff/takeoff_gui/lib/features/create/controllers/create_controller.dart @@ -64,7 +64,7 @@ abstract class _CreateController with Store { repoProvider = CloudProvidersComb.cicd[cloudProvider]![0]; } - void createProject() async { + void createProject() { monitorController.monitorProcess(() async => formController.create( backendLanguage: backendLanguage, backendVersion: backendVersion, diff --git a/takeoff/takeoff_gui/lib/features/create/widgets/project_forms/google_form.dart b/takeoff/takeoff_gui/lib/features/create/widgets/project_forms/google_form.dart index 0681f86cb..7919aedef 100644 --- a/takeoff/takeoff_gui/lib/features/create/widgets/project_forms/google_form.dart +++ b/takeoff/takeoff_gui/lib/features/create/widgets/project_forms/google_form.dart @@ -50,33 +50,25 @@ class GoogleForm extends StatelessWidget { ], ), const SizedBox(height: 15), - Row( - children: [ - Expanded( - child: Observer(builder: (_) { - if (controller.region.isEmpty) { - controller.region = googleCloudRegions.first; - } + Observer(builder: (_) { + if (controller.region.isEmpty) { + controller.region = googleCloudRegions.first; + } - return DropdownButtonFormField( - decoration: InputDecoration( - label: Text(AppLocalizations.of(context)!.region), - border: const OutlineInputBorder(), - ), - items: googleCloudRegions - .map((e) => DropdownMenuItem( - value: e, - child: Text(e), - )) - .toList(), - value: controller.region, - onChanged: (value) => controller.region = value!); - }), - ), - const SizedBox(width: 20), - Expanded(child: Container()), - ], - ), + return DropdownButtonFormField( + decoration: InputDecoration( + label: Text(AppLocalizations.of(context)!.region), + border: const OutlineInputBorder(), + ), + items: googleCloudRegions + .map((e) => DropdownMenuItem( + value: e, + child: Text(e), + )) + .toList(), + value: controller.region, + onChanged: (value) => controller.region = value!); + }), ], ); } diff --git a/takeoff/takeoff_gui/lib/features/details/pages/project_details.dart b/takeoff/takeoff_gui/lib/features/details/pages/project_details.dart index f55b35a87..50589b182 100644 --- a/takeoff/takeoff_gui/lib/features/details/pages/project_details.dart +++ b/takeoff/takeoff_gui/lib/features/details/pages/project_details.dart @@ -16,20 +16,22 @@ class ProjectDetails extends StatelessWidget { body: Row( children: [ SideBar(), - Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // TODO add dropdown to select project here - const SizedBox(height: 40), - Text( - "${project.name} ${AppLocalizations.of(context)!.projectResources}", - style: const TextStyle(fontSize: 30), - ), - const SizedBox(height: 40), - ResourceDetails(), - ], + Expanded( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // TODO add dropdown to select project here + const SizedBox(height: 40), + Text( + "${project.name} ${AppLocalizations.of(context)!.projectResources}", + style: const TextStyle(fontSize: 30), + ), + const SizedBox(height: 40), + ResourceDetails(), + ], + ), ), ) ], diff --git a/takeoff/takeoff_gui/lib/features/details/widgets/clean_dialog.dart b/takeoff/takeoff_gui/lib/features/details/widgets/clean_dialog.dart index 0f0a83129..292c5e1af 100644 --- a/takeoff/takeoff_gui/lib/features/details/widgets/clean_dialog.dart +++ b/takeoff/takeoff_gui/lib/features/details/widgets/clean_dialog.dart @@ -15,7 +15,7 @@ class CleanDialog extends StatelessWidget { backgroundColor: Colors.red.shade200, title: Text( AppLocalizations.of(context)!.removeProject, - style: TextStyle(fontSize: 30), + style: const TextStyle(fontSize: 30), ), content: SingleChildScrollView( child: Padding( diff --git a/takeoff/takeoff_gui/lib/features/quickstart/controllers/quickstart_controller.dart b/takeoff/takeoff_gui/lib/features/quickstart/controllers/quickstart_controller.dart index e88ba1151..b67903795 100644 --- a/takeoff/takeoff_gui/lib/features/quickstart/controllers/quickstart_controller.dart +++ b/takeoff/takeoff_gui/lib/features/quickstart/controllers/quickstart_controller.dart @@ -26,12 +26,13 @@ abstract class _QuickstartController with Store { @computed bool get isValidForm => billingAccount.isNotEmpty && region.isNotEmpty; - void createWayat() { - monitorController.monitorProcess(() async => await facade.quickstartWayat( - billingAccount: billingAccount, - googleCloudRegion: region, - outputStream: monitorController.outputChannel, - inputStream: monitorController.inputChannel)); + Future createWayat() async { + await monitorController.monitorProcess(() async => + await facade.quickstartWayat( + billingAccount: billingAccount, + googleCloudRegion: region, + outputStream: monitorController.outputChannel, + inputStream: monitorController.inputChannel)); } @action diff --git a/takeoff/takeoff_gui/test/features/common/custom_button_test.dart b/takeoff/takeoff_gui/test/common/custom_button_test.dart similarity index 100% rename from takeoff/takeoff_gui/test/features/common/custom_button_test.dart rename to takeoff/takeoff_gui/test/common/custom_button_test.dart diff --git a/takeoff/takeoff_gui/test/common/error_loading_test.dart b/takeoff/takeoff_gui/test/common/error_loading_test.dart new file mode 100644 index 000000000..6b26e52f1 --- /dev/null +++ b/takeoff/takeoff_gui/test/common/error_loading_test.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:takeoff_gui/common/error_loading_page.dart'; + +// @GenerateMocks([ClassToMock]) +void main() async { + setUpAll(() async {}); + + Widget createApp(Widget body) { + return MaterialApp( + onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + localeResolutionCallback: + (Locale? locale, Iterable supportedLocales) { + for (Locale supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale?.languageCode) { + return supportedLocale; + } + } + return const Locale("en", "US"); + }, + home: Scaffold( + body: body, + ), + ); + } + + testWidgets('Widget error widget', (tester) async { + // Avoid overflow due to test conditions + FlutterError.onError = null; + String messageText = "TestText"; + IconData icon = Icons.warning_amber_outlined; + await tester.pumpWidget(createApp(ErrorLoadingPage(message: messageText))); + expect(find.text(messageText), findsOneWidget); + expect(find.byIcon(icon), findsOneWidget); + }); +} diff --git a/takeoff/takeoff_gui/test/common/icon_text_button_test.dart b/takeoff/takeoff_gui/test/common/icon_text_button_test.dart new file mode 100644 index 000000000..0f10a18b1 --- /dev/null +++ b/takeoff/takeoff_gui/test/common/icon_text_button_test.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:takeoff_gui/common/icon_text_button.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +// @GenerateMocks([ClassToMock]) +void main() async { + setUpAll(() async {}); + + Widget createApp(Widget body) { + return MaterialApp( + onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + localeResolutionCallback: + (Locale? locale, Iterable supportedLocales) { + for (Locale supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale?.languageCode) { + return supportedLocale; + } + } + return const Locale("en", "US"); + }, + home: Scaffold( + body: body, + ), + ); + } + + testWidgets('Widget test', (tester) async { + // Avoid overflow due to test conditions + FlutterError.onError = null; + String buttonText = "TestText"; + IconData icon = Icons.account_circle; + await tester.pumpWidget(createApp( + IconTextButton( + text: buttonText, + icon: icon, + onPressed: () => true, + ), + )); + await tester.pumpWidget(createApp(IconTextButton( + icon: icon, + text: buttonText, + ))); + + await tester.pumpAndSettle(); + + expect(find.text(buttonText), findsOneWidget); + expect(find.byIcon(icon), findsOneWidget); + }); +} diff --git a/takeoff/takeoff_gui/test/common/loading_page_test.dart b/takeoff/takeoff_gui/test/common/loading_page_test.dart new file mode 100644 index 000000000..afdf1908c --- /dev/null +++ b/takeoff/takeoff_gui/test/common/loading_page_test.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:takeoff_gui/common/loading_page.dart'; + +// @GenerateMocks([ClassToMock]) +void main() async { + setUpAll(() async {}); + + Widget createApp(Widget body) { + return MaterialApp( + onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + localeResolutionCallback: + (Locale? locale, Iterable supportedLocales) { + for (Locale supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale?.languageCode) { + return supportedLocale; + } + } + return const Locale("en", "US"); + }, + home: Scaffold( + body: body, + ), + ); + } + + testWidgets('Widget loading page', (tester) async { + // Avoid overflow due to test conditions + String text = + "Launching the app, please while checking the requirements..."; + ImageProvider image = const AssetImage("assets/gifs/rocket.gif"); + await tester.pumpWidget(createApp(LoadingPage( + message: text, + ))); + expect(find.text(text), findsOneWidget); + expect(find.byType(Image), findsOneWidget); + expect(find.widgetWithImage(Image, image), findsNothing); + }); +} diff --git a/takeoff/takeoff_gui/test/common/monitor/controllers/monitor_controller_test.dart b/takeoff/takeoff_gui/test/common/monitor/controllers/monitor_controller_test.dart new file mode 100644 index 000000000..b5c4115d6 --- /dev/null +++ b/takeoff/takeoff_gui/test/common/monitor/controllers/monitor_controller_test.dart @@ -0,0 +1,58 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:takeoff_gui/common/monitor/controllers/monitor_controller.dart'; +import 'package:takeoff_lib/takeoff_lib.dart'; + +//@GenerateNiceMocks([]) +void main() async { + late MonitorController monitorController; + + setUpAll(() async { + monitorController = MonitorController(); + }); + + test('Test monitor hasFinished', () async { + bool result = monitorController.hasFinished; + expect(result, false); + }); + test('Test monitor isSuccess', () async { + bool result = monitorController.isSuccess; + expect(result, false); + }); + + test('Test monitor process', () async { + GuiMessage event = GuiMessage( + type: MessageType.success, + message: "Test GuiMessage - Seccess", + url: "Test url"); + await monitorController + .monitorProcess(() => {monitorController.projectUrl = "Test url"}); + expect(monitorController.projectUrl, event.url); + + event = + GuiMessage(type: MessageType.error, message: "Test GuiMessage - Error"); + await monitorController + .monitorProcess(() => {monitorController.projectUrl = ""}); + expect(monitorController.projectUrl, ""); + }); + + test('Test reset channel', () async { + GuiMessage event = GuiMessage( + type: MessageType.success, + message: "Test GuiMessage - outputChannel stream"); + String message = "Test inputChannel stream value"; + + monitorController.inputChannel.add("Test inputChannel stream value"); + monitorController.outputChannel.add(event); + + bool result = monitorController.inputChannel.stream.take(1) == message; + expect(result, false); + + result = monitorController.outputChannel.stream.take(1) == event; + expect(result, false); + + monitorController.resetChannel(); + + result = monitorController.outputChannel.stream.take(1).isBroadcast; + expect(result, true); + }); +} diff --git a/takeoff/takeoff_gui/test/common/monitor/pages/monitor_dialog_test.dart b/takeoff/takeoff_gui/test/common/monitor/pages/monitor_dialog_test.dart new file mode 100644 index 000000000..79bc6d48f --- /dev/null +++ b/takeoff/takeoff_gui/test/common/monitor/pages/monitor_dialog_test.dart @@ -0,0 +1,84 @@ +//@GenerateNiceMocks([]) +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mobx/mobx.dart' as mobx; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:takeoff_gui/common/custom_button.dart'; +import 'package:takeoff_gui/common/monitor/controllers/monitor_controller.dart'; +import 'package:takeoff_gui/common/monitor/pages/monitor_dialog.dart'; +import 'package:takeoff_gui/features/create/utils/create_message.dart'; +import 'package:takeoff_gui/features/create/utils/type_message.dart'; +import 'package:takeoff_gui/features/home/controllers/projects_controller.dart'; +import 'package:takeoff_lib/takeoff_lib.dart'; + +import '../../test_widget.dart'; +import 'monitor_dialog_test.mocks.dart'; + +@GenerateNiceMocks( + [MockSpec(), MockSpec()]) +void main() async { + final MockMonitorController mockMonitorController = MockMonitorController(); + final MockProjectsController mockProjectsController = + MockProjectsController(); + late MonitorDialog monitorDialog; + + setUpAll(() async { + GetIt.I.registerSingleton(mockMonitorController); + GetIt.I.registerSingleton(mockProjectsController); + monitorDialog = MonitorDialog(); + }); + + testWidgets('Widget has correct data when Project is created and success', + (tester) async { + CreateMessage message = + CreateMessage(TypeMessage.success, "Test message - isSuccess = true"); + + when(mockMonitorController.outputChannel).thenReturn(StreamController()); + when(mockMonitorController.steps) + .thenReturn(mobx.ObservableList.of([message])); + when(mockMonitorController.hasFinished).thenReturn(true); + when(mockMonitorController.isSuccess).thenReturn(true); + await tester.pumpWidget(TestWidget(child: MonitorDialog())); + + expect(find.text("Project creation finished"), findsOneWidget); + expect(find.byType(CustomButton), findsOneWidget); + expect(find.byIcon(Icons.browser_updated_outlined), findsOneWidget); + expect(find.text("Open project"), findsOneWidget); + }); + + testWidgets('Widget has correct data when Project is created and not success', + (tester) async { + CreateMessage message = + CreateMessage(TypeMessage.success, "Test message - isSuccess = false"); + + when(mockMonitorController.outputChannel).thenReturn(StreamController()); + when(mockMonitorController.steps) + .thenReturn(mobx.ObservableList.of([message])); + when(mockMonitorController.hasFinished).thenReturn(true); + when(mockMonitorController.isSuccess).thenReturn(false); + await tester.pumpWidget(TestWidget(child: MonitorDialog())); + + expect(find.text("Project creation finished"), findsOneWidget); + expect(find.byType(CustomButton), findsOneWidget); + expect(find.byIcon(Icons.browser_updated_outlined), findsOneWidget); + expect(find.text("Close"), findsOneWidget); + }); + + testWidgets('Widget has correct data when Creating project...', + (tester) async { + CreateMessage message = + CreateMessage(TypeMessage.success, "Test message - success"); + + when(mockMonitorController.outputChannel).thenReturn(StreamController()); + when(mockMonitorController.steps) + .thenReturn(mobx.ObservableList.of([message])); + when(mockMonitorController.hasFinished).thenReturn(false); + await tester.pumpWidget(TestWidget(child: MonitorDialog())); + + expect(find.text("Creating project..."), findsOneWidget); + }); +} diff --git a/takeoff/takeoff_gui/test/common/monitor/pages/user_interaction_dialog_test.dart b/takeoff/takeoff_gui/test/common/monitor/pages/user_interaction_dialog_test.dart new file mode 100644 index 000000000..81eb9fd9c --- /dev/null +++ b/takeoff/takeoff_gui/test/common/monitor/pages/user_interaction_dialog_test.dart @@ -0,0 +1,49 @@ +//@GenerateNiceMocks([]) +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/annotations.dart'; +import 'package:takeoff_gui/common/monitor/controllers/monitor_controller.dart'; +import 'package:takeoff_gui/common/monitor/pages/user_interaction_dialog.dart'; +import 'package:takeoff_lib/takeoff_lib.dart'; + +import '../../test_widget.dart'; +import 'user_interaction_dialog_test.mocks.dart'; + +@GenerateNiceMocks([ + MockSpec(), +]) +void main() async { + final MockMonitorController mockMonitorController = MockMonitorController(); + + setUpAll(() async { + GetIt.I.registerSingleton(mockMonitorController); + }); + + testWidgets('Widget has correct data', (tester) async { + GuiMessage guiMessage = + GuiMessage(type: MessageType.info, message: "Test Message"); + UserInteractionDialog userInteractionDialog = + UserInteractionDialog(message: guiMessage); + + userInteractionDialog.message.inputType = InputType.text; + userInteractionDialog.message.url = "Test url"; + + await tester.pumpWidget( + TestWidget(child: UserInteractionDialog(message: guiMessage))); + + expect(find.text("Please, follow these steps"), findsOneWidget); + expect(find.byType(SelectableText), findsOneWidget); + expect(find.byType(TextField), findsOneWidget); + expect(find.text("Open link"), findsOneWidget); + expect(find.text("Confirm"), findsOneWidget); + expect(find.byIcon(Icons.check_box), findsWidgets); + + userInteractionDialog.message.inputType = null; + await tester.tap(find.text("Open link")); + await tester.pumpAndSettle(); + + // await tester.tap(find.text("Confirm")); + // await tester.pumpAndSettle(); + }); +} diff --git a/takeoff/takeoff_gui/test/features/create/controllers/create_controller_test.dart b/takeoff/takeoff_gui/test/features/create/controllers/create_controller_test.dart new file mode 100644 index 000000000..0e0bdbd0b --- /dev/null +++ b/takeoff/takeoff_gui/test/features/create/controllers/create_controller_test.dart @@ -0,0 +1,79 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/annotations.dart'; +import 'package:takeoff_gui/common/monitor/controllers/monitor_controller.dart'; +import 'package:takeoff_gui/features/create/controllers/create_controller.dart'; +import 'package:takeoff_gui/features/create/controllers/project_form_controllers/create_form_controller.dart'; +import 'package:takeoff_gui/features/create/controllers/project_form_controllers/project_form_controllers.dart'; +import 'package:takeoff_gui/features/create/utils/provider_ci_cd.dart'; +import 'package:takeoff_lib/takeoff_lib.dart'; + +import 'create_controller_test.mocks.dart'; + +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), + MockSpec(), +]) +void main() async { + final MockCreateFormController mockCreateFormController = + MockCreateFormController(); + final MockGoogleFormController mockGoogleFormController = + MockGoogleFormController(); + final MockMonitorController mockMonitorController = MockMonitorController(); + late CreateController createController; + + setUpAll(() async { + GetIt.I.registerSingleton(mockMonitorController); + GetIt.I.registerSingleton(mockCreateFormController); + GetIt.I.registerSingleton(mockGoogleFormController); + + createController = CreateController(); + }); + + ///createProject cannot be tested because of asynchronic behavior and that the internal functions should be tested instead + // test('Test create project google cloud', () async { + // await createController.createProject(); + // verify(await mockCreateFormController.create( + // backendLanguage: Language.node, + // backendVersion: LanguagesVersions.backendLanguages.last.name, + // frontendLanguage: Language.angular, + // frontendVersion: LanguagesVersions.frontendLanguages.last.name)) + // .called(1); + // }); + + test('Form is valid', () async { + bool result = createController.isValid; + expect(result, false); + }); + + test('Test set cloud provider', () async { + CloudProviderId cloud = CloudProviderId.gcloud; + createController.setCloudProvider(cloud); + expect(createController.cloudProvider, cloud); + expect(createController.repoProvider, ProviderCICD.gcloud); + }); + + test('Test set default frontend language', () async { + Language lang = Language.angular; + createController.setFrontendLanguage(lang); + expect(createController.frontendLanguage.name, lang.name); + }); + + test('Test set default backend language', () async { + Language lang = Language.quarkusJVM; + createController.setBackendLanguage(lang); + expect(createController.backendLanguage.name, lang.name); + }); + + test('Test reset form', () async { + createController.resetForm(); + + expect(createController.cloudProvider, CloudProviderId.gcloud); + expect(createController.repoProvider, ProviderCICD.gcloud); + expect(createController.frontendLanguage, Language.flutter); + expect(createController.frontendVersion, '3.0.0'); + expect(createController.backendLanguage, Language.python); + expect(createController.backendVersion, "3.9"); + }); +} diff --git a/takeoff/takeoff_gui/test/features/create/controllers/project_form_controllers/google_form_controller_test.dart b/takeoff/takeoff_gui/test/features/create/controllers/project_form_controllers/google_form_controller_test.dart new file mode 100644 index 000000000..f5e10dba3 --- /dev/null +++ b/takeoff/takeoff_gui/test/features/create/controllers/project_form_controllers/google_form_controller_test.dart @@ -0,0 +1,42 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/annotations.dart'; +import 'package:takeoff_gui/features/create/controllers/project_form_controllers/google_form_controller.dart'; +import 'package:takeoff_lib/takeoff_lib.dart'; + +import 'google_form_controller_test.mocks.dart'; + +@GenerateNiceMocks([ + MockSpec(), +]) +void main() async { + final MockTakeOffFacade mockTakeOffFacade = MockTakeOffFacade(); + late GoogleFormController googleFormController; + + setUpAll(() async { + GetIt.I.registerSingleton(mockTakeOffFacade); + googleFormController = GoogleFormController(); + }); + + test('Test create project google cloud', () async { + await googleFormController.create(); + bool result = await mockTakeOffFacade.createProjectGCloud( + projectName: "TestProjectName", + billingAccount: "0000-0000-0000-0000", + googleCloudRegion: "europe-west1"); + expect(result, false); + }); + + test('Form is valid', () async { + bool result = googleFormController.isValid; + expect(result, false); + }); + + test('Test reset form', () async { + googleFormController.resetForm(); + + expect(googleFormController.projectName, ""); + expect(googleFormController.billingAccount, ""); + expect(googleFormController.region, ""); + }); +} diff --git a/takeoff/takeoff_gui/test/features/create/pages/create_dialog_test.dart b/takeoff/takeoff_gui/test/features/create/pages/create_dialog_test.dart new file mode 100644 index 000000000..091c8cfe3 --- /dev/null +++ b/takeoff/takeoff_gui/test/features/create/pages/create_dialog_test.dart @@ -0,0 +1,43 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:takeoff_gui/features/create/controllers/create_controller.dart'; +import 'package:takeoff_gui/features/create/controllers/project_form_controllers/google_form_controller.dart'; +import 'package:takeoff_gui/features/create/pages/create_dialog.dart'; +import 'package:takeoff_gui/features/create/utils/languages_versions.dart'; +import 'package:takeoff_lib/takeoff_lib.dart'; + +import '../../../common/test_widget.dart'; +import 'create_dialog_test.mocks.dart'; + +@GenerateNiceMocks( + [MockSpec(), MockSpec()]) +void main() async { + MockCreateController mockCreateController = MockCreateController(); + MockGoogleFormController mockGoogleFormController = + MockGoogleFormController(); + + setUpAll(() async { + GetIt.I.registerSingleton(mockCreateController); + GetIt.I.registerSingleton(mockGoogleFormController); + }); + + testWidgets('Widget has text', (tester) async { + when(mockGoogleFormController.projectName).thenReturn("TestProjectName"); + when(mockGoogleFormController.billingAccount) + .thenReturn("0000-0000-0000-0000"); + when(mockGoogleFormController.region).thenReturn("europe-west2"); + + when(mockCreateController.backendLanguage).thenReturn(Language.python); + when(mockCreateController.backendVersion).thenReturn(LanguagesVersions + .versionsLanguages[LanguagesVersions.backendLanguages.first]!.first); + + when(mockCreateController.frontendLanguage).thenReturn(Language.flutter); + when(mockCreateController.frontendVersion).thenReturn(LanguagesVersions + .versionsLanguages[LanguagesVersions.frontendLanguages.first]!.first); + + await tester.pumpWidget(TestWidget(child: CreateDialog())); + expect(find.text("Create a project"), findsOneWidget); + }); +} diff --git a/takeoff/takeoff_gui/test/features/create/utils/create_message_test.dart b/takeoff/takeoff_gui/test/features/create/utils/create_message_test.dart new file mode 100644 index 000000000..e7527adbd --- /dev/null +++ b/takeoff/takeoff_gui/test/features/create/utils/create_message_test.dart @@ -0,0 +1,60 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:takeoff_gui/features/create/utils/create_message.dart'; +import 'package:takeoff_gui/features/create/utils/type_message.dart'; +import 'package:takeoff_lib/takeoff_lib.dart'; + +void main() async { + TypeMessage typeMessage; + String message; + + setUpAll(() async {}); + + test("Test create message", () async { + typeMessage = TypeMessage.info; + message = "Test message - Info"; + CreateMessage createMessage = CreateMessage(typeMessage, message); + expect(createMessage.typeMessage, typeMessage); + expect(createMessage.message, message); + + typeMessage = TypeMessage.error; + message = "Test message - Error"; + createMessage = CreateMessage(typeMessage, message); + expect(createMessage.typeMessage, typeMessage); + expect(createMessage.message, message); + }); + + test("Test create message factory", () async { + typeMessage = TypeMessage.error; + message = "Test message - Error"; + GuiMessage guiMessage = + GuiMessage(type: MessageType.error, message: message); + + CreateMessage createMessage = CreateMessage.fromGuiMessage(guiMessage); + expect(createMessage.typeMessage, typeMessage); + expect(createMessage.message, message); + + typeMessage = TypeMessage.success; + message = "Test message - Success"; + guiMessage = GuiMessage(type: MessageType.success, message: message); + + createMessage = CreateMessage.fromGuiMessage(guiMessage); + expect(createMessage.typeMessage, typeMessage); + expect(createMessage.message, message); + + typeMessage = TypeMessage.info; + message = "Test message - Info"; + guiMessage = GuiMessage(type: MessageType.info, message: message); + + createMessage = CreateMessage.fromGuiMessage(guiMessage); + expect(createMessage.typeMessage, typeMessage); + expect(createMessage.message, message); + + typeMessage = TypeMessage.action; + message = "Test message - Action"; + guiMessage = GuiMessage(type: MessageType.input, message: message); + + createMessage = CreateMessage.fromGuiMessage(guiMessage); + expect(createMessage.typeMessage, typeMessage); + expect(createMessage.message, message); + }); +} diff --git a/takeoff/takeoff_gui/test/features/create/utils/widgets/repo_selector_test.dart b/takeoff/takeoff_gui/test/features/create/utils/widgets/repo_selector_test.dart new file mode 100644 index 000000000..304ee9aaf --- /dev/null +++ b/takeoff/takeoff_gui/test/features/create/utils/widgets/repo_selector_test.dart @@ -0,0 +1,40 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:takeoff_gui/features/create/controllers/create_controller.dart'; +import 'package:get_it/get_it.dart'; +import 'package:takeoff_gui/features/create/utils/provider_ci_cd.dart'; +import 'package:takeoff_gui/features/create/widgets/repo_selector.dart'; + +import '../../../../common/test_widget.dart'; +import 'repo_selector_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() async { + MockCreateController mockCreateController = MockCreateController(); + + setUpAll(() async { + GetIt.I.registerSingleton(mockCreateController); + }); + + testWidgets('Widget has correct data', (tester) async { + await tester.pumpWidget(TestWidget(child: RepoSelector())); + + expect(find.text("Select a repo & CI/CD provider"), findsOneWidget); + expect(mockCreateController.repoProvider, ProviderCICD.github); + + bool result = + mockCreateController.providersCICD.contains(ProviderCICD.gcloud); + expect(result, false); + + result = + mockCreateController.providersCICD.contains(ProviderCICD.azureDevOps); + expect(result, false); + + result = mockCreateController.providersCICD.contains(ProviderCICD.github); + expect(result, false); + + when(mockCreateController.providersCICD.contains(ProviderCICD.gcloud)) + .thenReturn(true); + }); +} diff --git a/takeoff/takeoff_gui/test/features/details/project_details_test.dart b/takeoff/takeoff_gui/test/features/details/project_details_test.dart new file mode 100644 index 000000000..a73997230 --- /dev/null +++ b/takeoff/takeoff_gui/test/features/details/project_details_test.dart @@ -0,0 +1,29 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:takeoff_gui/domain/project.dart'; +import 'package:takeoff_gui/features/details/pages/project_details.dart'; +import 'package:takeoff_gui/features/home/controllers/projects_controller.dart'; +import 'package:takeoff_lib/takeoff_lib.dart'; + +import '../../common/test_widget.dart'; +import 'project_details_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() async { + MockProjectsController mockProjectsController = MockProjectsController(); + final Project project = + Project(name: "TestProject", cloud: CloudProviderId.gcloud); + + setUpAll(() async { + GetIt.I.registerSingleton(mockProjectsController); + when(mockProjectsController.selectedProject).thenReturn(project); + }); + + testWidgets('Widget loading page', (tester) async { + String text = "${project.name} project resources"; + await tester.pumpWidget(TestWidget(child: ProjectDetails())); + expect(find.text(text), findsOneWidget); + }); +} diff --git a/takeoff/takeoff_gui/test/features/details/widgets/clean_dialog_test.dart b/takeoff/takeoff_gui/test/features/details/widgets/clean_dialog_test.dart new file mode 100644 index 000000000..68a536fd4 --- /dev/null +++ b/takeoff/takeoff_gui/test/features/details/widgets/clean_dialog_test.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:takeoff_gui/common/custom_button.dart'; +import 'package:takeoff_gui/domain/project.dart'; +import 'package:takeoff_gui/features/details/widgets/clean_dialog.dart'; +import 'package:takeoff_gui/features/home/controllers/projects_controller.dart'; +import 'package:takeoff_lib/takeoff_lib.dart'; + +import '../../../common/test_widget.dart'; +import '../project_details_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() async { + MockProjectsController mockProjectsController = MockProjectsController(); + final Project project = + Project(name: "TestProject", cloud: CloudProviderId.gcloud); + + setUpAll(() async { + GetIt.I.registerSingleton(mockProjectsController); + when(mockProjectsController.selectedProject).thenReturn(project); + }); + + testWidgets('Widget has correct text', (tester) async { + await tester.pumpWidget(TestWidget(child: CleanDialog())); + expect(find.text("Remove project"), findsOneWidget); + expect( + find.text( + "The project will be deleted from local cache, but not remove from cloud."), + findsOneWidget); + expect(find.text("Once removed, you won't be able to add it again."), + findsOneWidget); + expect(find.text("Remove"), findsOneWidget); + expect(find.text("Close"), findsOneWidget); + + await tester.pumpWidget(TestWidget( + child: CustomButton( + text: 'Remove', icon: Icons.remove, onPressed: () => {}))); + await tester.pumpWidget(TestWidget( + child: CustomButton( + text: 'Close', icon: Icons.close, onPressed: () => {}))); + }); +} diff --git a/takeoff/takeoff_gui/test/features/quickstart/conmtrollers/quickstart_controller_test.dart b/takeoff/takeoff_gui/test/features/quickstart/conmtrollers/quickstart_controller_test.dart new file mode 100644 index 000000000..83c631887 --- /dev/null +++ b/takeoff/takeoff_gui/test/features/quickstart/conmtrollers/quickstart_controller_test.dart @@ -0,0 +1,46 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/annotations.dart'; +import 'package:takeoff_gui/common/monitor/controllers/monitor_controller.dart'; +import 'package:takeoff_gui/features/quickstart/controllers/quickstart_controller.dart'; +import 'package:takeoff_lib/takeoff_lib.dart'; + +import 'quickstart_controller_test.mocks.dart'; + +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), +]) +void main() async { + final MockTakeOffFacade mockTakeOffFacade = MockTakeOffFacade(); + final MockMonitorController mockMonitorController = MockMonitorController(); + late QuickstartController quickstartController; + + setUpAll(() async { + GetIt.I.registerSingleton(mockMonitorController); + GetIt.I.registerSingleton(mockTakeOffFacade); + quickstartController = QuickstartController(); + }); + + ///createProject cannot be tested because of asynchronic behavior and that the internal functions should be tested instead + // test('Test create Wayat google cloud', () async { + // await quickstartController.createWayat(); + // verify(await mockTakeOffFacade.quickstartWayat( + // billingAccount: "1111-1111-1111-1111", + // googleCloudRegion: "europe-east1", + // )) + // .called(1); + // }); + + test('Form is valid', () async { + bool result = quickstartController.isValidForm; + expect(result, false); + }); + + test('Test reset form', () async { + quickstartController.resetForm(); + + expect(quickstartController.billingAccount, ""); + expect(quickstartController.region, ""); + }); +} diff --git a/takeoff/takeoff_gui/test/features/quickstart/pages/quickstart_dialog_test.dart b/takeoff/takeoff_gui/test/features/quickstart/pages/quickstart_dialog_test.dart new file mode 100644 index 000000000..6a07e4198 --- /dev/null +++ b/takeoff/takeoff_gui/test/features/quickstart/pages/quickstart_dialog_test.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:takeoff_gui/features/create/controllers/project_form_controllers/google_form_controller.dart'; +import 'package:takeoff_gui/features/quickstart/controllers/quickstart_controller.dart'; +import 'package:takeoff_gui/features/quickstart/pages/quickstart_dialog.dart'; +import 'package:takeoff_gui/features/quickstart/utils/apps.dart'; +import 'package:takeoff_gui/features/quickstart/widgets/quickstart_card.dart'; +import 'package:takeoff_lib/takeoff_lib.dart'; + +import '../../../common/test_widget.dart'; +import 'quickstart_dialog_test.mocks.dart'; + +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), + MockSpec(), +]) +void main() async { + final MockQuickstartController mockQuickstartController = + MockQuickstartController(); + final MockGoogleFormController mockGoogleFormController = + MockGoogleFormController(); + final MockTakeOffFacade mockTakeOffFacade = MockTakeOffFacade(); + + setUpAll(() async { + GetIt.I.registerSingleton(mockQuickstartController); + GetIt.I.registerSingleton(mockGoogleFormController); + GetIt.I.registerSingleton(mockTakeOffFacade); + }); + + testWidgets('Widget has text', (tester) async { + when(mockQuickstartController.app).thenReturn(Apps.wayat); + when(mockQuickstartController.region).thenReturn("asia-southeast1"); + when(mockQuickstartController.billingAccount) + .thenReturn("3333-3333-3333-3333"); + + await tester.pumpWidget(const TestWidget( + child: QuickstartDialog(), + )); + expect(find.byType(QuickstartCard), findsWidgets); + expect(find.byType(AssetImage), findsNothing); + + when(mockQuickstartController.app).thenReturn(Apps.viplane); + await tester.pumpWidget(const TestWidget( + child: QuickstartDialog(), + )); + }); +}