Skip to content

Commit

Permalink
Merge pull request #902 from Prime-Holding/feature/864-update-goldens…
Browse files Browse the repository at this point in the history
…-in-reminder-app-with-alchemist

Feature/864 update goldens in reminder app with alchemist
  • Loading branch information
RomanovaPrime authored Dec 12, 2024
2 parents 6138e71 + 8c3ca1d commit e824a6b
Show file tree
Hide file tree
Showing 79 changed files with 1,390 additions and 168 deletions.
6 changes: 3 additions & 3 deletions examples/reminders/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ To access the design system from your app, you have to import it from the follow

### Golden tests

A [golden test][golden_test_lnk] lets you generate golden master images of a widget or screen, and compare against them so you know your design is always pixel-perfect and there have been no subtle or breaking changes in UI between builds. To make this easier, we employ the use of the [golden_toolkit][golden_toolkit_lnk] package.
A [golden test][golden_test_lnk] lets you generate golden master images of a widget or screen, and compare against them so you know your design is always pixel-perfect and there have been no subtle or breaking changes in UI between builds. To make this easier, we employ the use of the [alchemist][alchemist_lnk] package.

To get started, you just need to generate a list of `LabeledDeviceBuilder` and pass it to the `runGoldenTests` function. That's done by calling `generateDeviceBuilder` with a label, the widget/screen to be tested, as well as a `Scenario`. They provide an optional `onCreate` function which lets us execute arbitrary behavior upon testing. Each `DeviceBuilder` will have two generated golden master files, one for each theme.
To get started, you just need to generate a list of `ScenarioBuilder` and pass it to the `runGoldenTests` function. That's done by calling `buildScenario` with a label, the widget/screen to be tested, as well as an optional list of `devices` to be build on. They provide an optional `customPumpBeforeTest` function which lets us execute `WidgetTesterCallback` upon testing. Each `DeviceBuilder` will have two generated golden master files, one for each theme.

Due to the way fonts are loaded in tests, any custom fonts you intend to golden test should be included in `pubspec.yaml`

Expand Down Expand Up @@ -156,7 +156,7 @@ In order to make the notifications work on your target platform, make sure you f
[firebase_configs_lnk]: https://support.google.com/firebase/answer/7015592
[design_system_lnk]: https://uxdesign.cc/everything-you-need-to-know-about-design-systems-54b109851969
[golden_test_lnk]: https://medium.com/flutter-community/flutter-golden-tests-compare-widgets-with-snapshots-27f83f266cea
[golden_toolkit_lnk]: https://pub.dev/packages/golden_toolkit
[alchemist_lnk]: https://pub.dev/packages/alchemist
[retrofit_lnk]: https://pub.dev/packages/retrofit
[dio_lnk]: https://pub.dev/packages/dio
[json_annotation_lnk]: https://pub.dev/packages/json_annotation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import 'dart:async';

import 'package:rx_bloc/rx_bloc.dart';
import 'package:rx_bloc_list/extensions.dart';
import 'package:rx_bloc_list/models.dart';
import 'package:rxdart/rxdart.dart';

Expand Down
14 changes: 14 additions & 0 deletions examples/reminders/lib/base/services/reminders_service.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:flutter_rx_bloc/rx_form.dart';
import 'package:rx_bloc_list/models.dart';

import '../models/reminder/reminder_model.dart';
Expand All @@ -10,6 +11,8 @@ class RemindersService {

final RemindersRepository _repository;

static const _nameValidation = 'A title must be specified';

/// Returns all reminders
Future<PaginatedList<ReminderModel>> getAll(ReminderModelRequest request) =>
_repository.getAll(request);
Expand Down Expand Up @@ -43,4 +46,15 @@ class RemindersService {

/// Returns the number of incomplete reminders
Future<int> getIncompleteCount() => _repository.getIncompleteCount();

String validateName(String name) {
if (name.trim().isEmpty) {
throw RxFieldException(
fieldValue: name,
error: _nameValidation,
);
}

return name;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:collection/collection.dart';
import 'package:rx_bloc/rx_bloc.dart';
import 'package:rx_bloc_list/extensions.dart';
import 'package:rx_bloc_list/models.dart';
import 'package:rxdart/rxdart.dart';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'dart:async';

import 'package:flutter_rx_bloc/rx_form.dart';
import 'package:rx_bloc/rx_bloc.dart';
import 'package:rxdart/rxdart.dart';

Expand All @@ -18,7 +17,6 @@ abstract class ReminderManageBlocEvents {
void update(ReminderModel reminder);

/// Event used to update the name of the current model
@RxBlocEvent(type: RxBlocEventType.behaviour, seed: '')
void setName(String title);

/// Event used to initiate creation of a new reminder with the provided
Expand Down Expand Up @@ -66,8 +64,6 @@ class ReminderManageBloc extends $ReminderManageBloc {
final RemindersService _service;
final CoordinatorBlocType _coordinatorBloc;

static const _nameValidation = 'A title must be specified';

@override
Stream<bool> _mapToShowErrorsState() => _$createEvent
.isReminderNameValid(this)
Expand Down Expand Up @@ -114,18 +110,9 @@ class ReminderManageBloc extends $ReminderManageBloc {
.publish();

@override
Stream<String> _mapToNameState() => _$setNameEvent.map(validateName);

String validateName(String name) {
if (name.trim().isEmpty) {
throw RxFieldException(
fieldValue: name,
error: _nameValidation,
);
}

return name;
}
Stream<String> _mapToNameState() => _$setNameEvent
.map((name) => _service.validateName(name))
.shareReplay(maxSize: 1);

@override
Stream<bool> _mapToIsFormValidState() => Rx.combineLatest<String, bool>(
Expand Down
30 changes: 19 additions & 11 deletions examples/reminders/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.40"
alchemist:
dependency: "direct main"
description:
name: alchemist
sha256: "2dcbfb0fe2b108797831241e48bb8d2abb902d2626332fb98b5680fb1f6bdb3a"
url: "https://pub.dev"
source: hosted
version: "0.11.0"
analyzer:
dependency: transitive
description:
Expand Down Expand Up @@ -233,6 +241,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
equatable:
dependency: transitive
description:
name: equatable
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
url: "https://pub.dev"
source: hosted
version: "2.0.7"
facebook_auth_desktop:
dependency: transitive
description:
Expand Down Expand Up @@ -500,14 +516,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.7.0"
golden_toolkit:
dependency: "direct main"
description:
name: golden_toolkit
sha256: "8f74adab33154fe7b731395782797021f97d2edc52f7bfb85ff4f1b5c4a215f0"
url: "https://pub.dev"
source: hosted
version: "0.15.0"
graphs:
dependency: transitive
description:
Expand Down Expand Up @@ -808,7 +816,7 @@ packages:
path: "../../packages/rx_bloc_list"
relative: true
source: path
version: "5.0.0"
version: "5.0.1"
rx_bloc_test:
dependency: "direct dev"
description:
Expand Down Expand Up @@ -1065,10 +1073,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev"
source: hosted
version: "14.2.4"
version: "14.2.5"
watcher:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion examples/reminders/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ environment:
sdk: '>=3.0.5 <4.0.0'

dependencies:
alchemist: ^0.11.0
cloud_firestore: ^5.2.0
collection: ^1.18.0
cupertino_icons: ^1.0.4
Expand All @@ -25,7 +26,6 @@ dependencies:
flutter_slidable: ^3.0.0
flutter_sticky_header: ^0.6.0
go_router: ^14.2.2
golden_toolkit: ^0.15.0
grouped_list: ^6.0.0
intl: ^0.19.0
provider: ^6.0.1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:flutter_rx_bloc/flutter_rx_bloc.dart';
import 'package:provider/provider.dart';
import 'package:reminders/base/common_blocs/firebase_bloc.dart';
import 'package:reminders/base/models/reminder/reminder_model.dart';
import 'package:reminders/feature_dashboard/blocs/dashboard_bloc.dart';
import 'package:reminders/feature_dashboard/models/dashboard_model.dart';
import 'package:reminders/feature_dashboard/views/dashboard_page.dart';
import 'package:reminders/feature_reminder_manage/blocs/reminder_manage_bloc.dart';
import 'package:rx_bloc/rx_bloc.dart';
import 'package:rx_bloc_list/models.dart';

import '../../mocks/bloc_mocks.dart';
import '../../mocks/reminder_manage_mock.dart';
import '../mock/dashboard_mock.dart';

Widget dashboardFactory({
bool? isLoading,
String? errors,
Result<DashboardCountersModel>? dashboardCounters,
PaginatedList<ReminderModel>? reminderModels,
}) =>
Scaffold(
body: MultiProvider(providers: [
RxBlocProvider<ReminderManageBlocType>.value(
value: reminderManageMockFactory()),
RxBlocProvider<FirebaseBlocType>.value(value: createFirebaseBlocMock()),
RxBlocProvider<DashboardBlocType>.value(
value: dashboardMockFactory(
isLoading: isLoading,
errors: errors,
dashboardCounters: dashboardCounters,
reminderModels: reminderModels,
),
),
], child: DashboardPage()),
);
48 changes: 48 additions & 0 deletions examples/reminders/test/feature_dashboard/mock/dashboard_mock.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:reminders/base/models/reminder/reminder_model.dart';
import 'package:reminders/feature_dashboard/blocs/dashboard_bloc.dart';
import 'package:reminders/feature_dashboard/models/dashboard_model.dart';
import 'package:rx_bloc/rx_bloc.dart';
import 'package:rx_bloc_list/models.dart';
import 'package:rxdart/rxdart.dart';

import 'dashboard_mock.mocks.dart';

@GenerateMocks([DashboardBlocStates, DashboardBlocEvents, DashboardBlocType])
DashboardBlocType dashboardMockFactory({
bool? isLoading,
String? errors,
Result<DashboardCountersModel>? dashboardCounters,
PaginatedList<ReminderModel>? reminderModels,
}) {
final blocMock = MockDashboardBlocType();
final eventsMock = MockDashboardBlocEvents();
final statesMock = MockDashboardBlocStates();

when(blocMock.events).thenReturn(eventsMock);
when(blocMock.states).thenReturn(statesMock);

final isLoadingState = isLoading != null
? Stream.value(isLoading).shareReplay(maxSize: 1)
: const Stream<bool>.empty();

final errorsState = errors != null
? Stream.value(errors).shareReplay(maxSize: 1)
: const Stream<String>.empty();

final dashboardCountersState = dashboardCounters != null
? Stream.value(dashboardCounters).shareReplay(maxSize: 1)
: const Stream<Result<DashboardCountersModel>>.empty();

final reminderModelsState = reminderModels != null
? Stream.value(reminderModels).shareReplay(maxSize: 1)
: const Stream<PaginatedList<ReminderModel>>.empty();

when(statesMock.isLoading).thenAnswer((_) => isLoadingState);
when(statesMock.errors).thenAnswer((_) => errorsState);
when(statesMock.dashboardCounters).thenAnswer((_) => dashboardCountersState);
when(statesMock.reminderModels).thenAnswer((_) => reminderModelsState);

return blocMock;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'package:rx_bloc/rx_bloc.dart';

import '../../helpers/golden_helper.dart';
import '../../stubs.dart';
import '../factory/dashboard_factory.dart';

void main() {
runGoldenTests([
buildScenario(widget: dashboardFactory(), scenario: 'dashboard_empty'),
buildScenario(
widget: dashboardFactory(
dashboardCounters: Result.success(Stubs.dashboardCountersModel),
reminderModels: Stubs.reminderPaginatedList,
), //example: Stubs.success
scenario: 'dashboard_success'),
buildScenario(
widget: dashboardFactory(dashboardCounters: Result.loading()),
scenario: 'dashboard_loading',
customPumpBeforeTest: (tester) =>
tester.pump(const Duration(milliseconds: 300))),
buildScenario(
widget: dashboardFactory(
dashboardCounters: Result.error(Stubs.throwable),
errors: Stubs.errorNoConnection),
scenario: 'dashboard_error')
]);
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:flutter_rx_bloc/flutter_rx_bloc.dart';
import 'package:provider/provider.dart';
import 'package:reminders/base/common_blocs/coordinator_bloc.dart';
import 'package:reminders/base/common_blocs/firebase_bloc.dart';
import 'package:reminders/base/models/reminder/reminder_model.dart';
import 'package:reminders/base/services/reminders_service.dart';
import 'package:reminders/feature_reminder_list/blocs/reminder_list_bloc.dart';
import 'package:reminders/feature_reminder_list/services/reminder_list_service.dart';
import 'package:reminders/feature_reminder_list/views/reminder_list_page.dart';
import 'package:reminders/feature_reminder_manage/blocs/reminder_manage_bloc.dart';
import 'package:rx_bloc_list/models.dart';

import '../../mocks/bloc_mocks.dart';
import '../../mocks/coordinator_mock.dart';
import '../../mocks/reminder_manage_mock.dart';
import '../../mocks/service_mocks.dart';
import '../mock/reminder_list_mock.dart';

Widget reminderListFactory({
bool? isLoading,
String? errors,
PaginatedList<ReminderModel>? paginatedList,
}) =>
Scaffold(
body: MultiProvider(providers: [
Provider<RemindersService>(
create: (context) => createRemindersServiceMock(),
),
Provider<ReminderListService>(
create: (context) => createReminderListServiceMock(),
),
RxBlocProvider<CoordinatorBlocType>.value(
value: coordinatorMockFactory(),
),
RxBlocProvider<ReminderListBlocType>.value(
value: reminderListMockFactory(
isLoading: isLoading,
errors: errors,
paginatedList: paginatedList,
),
),
RxBlocProvider<FirebaseBlocType>.value(
value: createFirebaseBlocMock(),
),
RxBlocProvider<ReminderManageBlocType>.value(
value: reminderManageMockFactory(),
),
], child: const ReminderListPage()),
);
Loading

0 comments on commit e824a6b

Please sign in to comment.