Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion lib/presentation/schedule_create/bloc/schedule_form_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import 'package:injectable/injectable.dart';
import 'package:on_time_front/core/dio/api_error_message.dart';
import 'package:on_time_front/domain/entities/place_entity.dart';
import 'package:on_time_front/domain/entities/preparation_entity.dart';
import 'package:on_time_front/domain/entities/preparation_step_entity.dart';
import 'package:on_time_front/domain/entities/schedule_entity.dart';
import 'package:on_time_front/domain/entities/schedule_preparation_mode.dart';
import 'package:on_time_front/domain/use-cases/create_custom_preparation_use_case.dart';
import 'package:on_time_front/domain/use-cases/create_schedule_with_place_use_case.dart';
import 'package:on_time_front/domain/use-cases/get_default_preparation_use_case.dart';
Expand Down Expand Up @@ -97,6 +99,7 @@ class ScheduleFormBloc extends Bloc<ScheduleFormEvent, ScheduleFormState> {
scheduleSpareTime: scheduleEntity.scheduleSpareTime,
scheduleNote: scheduleEntity.scheduleNote,
preparation: preparationEntity,
originalPreparationMode: scheduleEntity.preparationMode,
),
);
}
Expand Down Expand Up @@ -224,7 +227,7 @@ class ScheduleFormBloc extends Bloc<ScheduleFormEvent, ScheduleFormState> {
await _updateScheduleUseCase(scheduleEntity);
if (state.isChanged != IsPreparationChanged.unchanged) {
await _updatePreparationByScheduleIdUseCase(
state.preparation!,
_preparationForScheduleUpdate(),
scheduleEntity.id,
);
}
Expand Down Expand Up @@ -311,6 +314,28 @@ class ScheduleFormBloc extends Bloc<ScheduleFormEvent, ScheduleFormState> {
);
}

PreparationEntity _preparationForScheduleUpdate() {
final preparation = state.preparation!;
if (state.originalPreparationMode !=
SchedulePreparationMode.defaultPreparation) {
return preparation;
}

final orderedSteps = preparation.ordered.preparationStepList;
final newIds = List.generate(orderedSteps.length, (_) => Uuid().v7());
final copiedSteps = <PreparationStepEntity>[
for (var i = 0; i < orderedSteps.length; i++)
PreparationStepEntity(
id: newIds[i],
preparationName: orderedSteps[i].preparationName,
preparationTime: orderedSteps[i].preparationTime,
nextPreparationId: i + 1 < newIds.length ? newIds[i + 1] : null,
),
];

return PreparationEntity(preparationStepList: copiedSteps);
}

Future<void> _trackScheduleCreated(ScheduleEntity scheduleEntity) async {
await _productUsageEventTracker.track(
ProductUsageEvent(
Expand Down
40 changes: 23 additions & 17 deletions lib/presentation/schedule_create/bloc/schedule_form_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ final class ScheduleFormState extends Equatable {
final Duration? scheduleSpareTime;
final String? scheduleNote;
final PreparationEntity? preparation;
final SchedulePreparationMode? originalPreparationMode;
final bool isValid;
final Duration? maxAvailableTime;
final String? previousScheduleName;
Expand All @@ -40,6 +41,7 @@ final class ScheduleFormState extends Equatable {
this.scheduleSpareTime,
this.scheduleNote,
this.preparation,
this.originalPreparationMode,
this.isValid = false,
this.maxAvailableTime,
this.previousScheduleName,
Expand All @@ -59,6 +61,7 @@ final class ScheduleFormState extends Equatable {
Duration? scheduleSpareTime,
String? scheduleNote,
PreparationEntity? preparation,
SchedulePreparationMode? originalPreparationMode,
bool? isValid,
Duration? maxAvailableTime,
String? previousScheduleName,
Expand All @@ -79,6 +82,8 @@ final class ScheduleFormState extends Equatable {
scheduleSpareTime: scheduleSpareTime ?? this.scheduleSpareTime,
scheduleNote: scheduleNote ?? this.scheduleNote,
preparation: preparation ?? this.preparation,
originalPreparationMode:
originalPreparationMode ?? this.originalPreparationMode,
isValid: isValid ?? this.isValid,
maxAvailableTime: maxAvailableTime ?? this.maxAvailableTime,
previousScheduleName: previousScheduleName ?? this.previousScheduleName,
Expand Down Expand Up @@ -111,21 +116,22 @@ final class ScheduleFormState extends Equatable {

@override
List<Object?> get props => [
status,
submissionStatus,
submissionError,
id,
placeId,
placeName,
scheduleName,
scheduleTime,
moveTime,
isChanged,
scheduleSpareTime,
scheduleNote,
preparation,
isValid,
maxAvailableTime,
previousScheduleName,
];
status,
submissionStatus,
submissionError,
id,
placeId,
placeName,
scheduleName,
scheduleTime,
moveTime,
isChanged,
scheduleSpareTime,
scheduleNote,
preparation,
originalPreparationMode,
isValid,
maxAvailableTime,
previousScheduleName,
];
}
116 changes: 116 additions & 0 deletions test/presentation/schedule_create/bloc/schedule_form_bloc_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:on_time_front/domain/entities/preparation_entity.dart';
import 'package:on_time_front/domain/entities/preparation_step_entity.dart';
import 'package:on_time_front/domain/entities/product_usage_event.dart';
import 'package:on_time_front/domain/entities/schedule_entity.dart';
import 'package:on_time_front/domain/entities/schedule_preparation_mode.dart';
import 'package:on_time_front/domain/entities/user_entity.dart';
import 'package:on_time_front/domain/use-cases/track_product_usage_event_use_case.dart';
import 'package:on_time_front/domain/use-cases/create_custom_preparation_use_case.dart';
Expand Down Expand Up @@ -477,6 +478,121 @@ void main() {
expect(preparationUpdateCount, 0);
});

test(
'ScheduleFormUpdated copies default preparation with fresh step IDs',
() async {
getScheduleByIdUseCase = StubGetScheduleByIdUseCase(
(_) async => schedule.copyWith(
preparationMode: SchedulePreparationMode.defaultPreparation,
),
);

PreparationEntity? updatedPreparation;
updatePreparationByScheduleIdUseCase =
StubUpdatePreparationByScheduleIdUseCase((preparation, _) async {
updatedPreparation = preparation;
});

final bloc = buildBloc();
addTearDown(bloc.close);

final editReady = bloc.stream.firstWhere(
(state) => state.status == ScheduleFormStatus.success,
);
bloc.add(const ScheduleFormEditRequested(scheduleId: 'schedule-1'));
await editReady;

const editedPreparation = PreparationEntity(
preparationStepList: [
PreparationStepEntity(
id: 'default-step-1',
preparationName: 'Makeup',
preparationTime: Duration(minutes: 20),
nextPreparationId: 'default-step-2',
),
PreparationStepEntity(
id: 'default-step-2',
preparationName: 'Bathroom',
preparationTime: Duration(minutes: 5),
),
],
);
bloc.add(
const ScheduleFormPreparationChanged(preparation: editedPreparation),
);

final submitDone = bloc.stream.firstWhere(
(state) =>
state.submissionStatus == ScheduleFormSubmissionStatus.success,
);
bloc.add(const ScheduleFormUpdated());
await submitDone;

final steps = updatedPreparation!.preparationStepList;
expect(steps, hasLength(2));
expect(steps[0].preparationName, 'Makeup');
expect(steps[0].preparationTime, const Duration(minutes: 20));
expect(steps[1].preparationName, 'Bathroom');
expect(steps[1].preparationTime, const Duration(minutes: 5));
expect(steps[0].id, isNot('default-step-1'));
expect(steps[1].id, isNot('default-step-2'));
expect(steps[0].nextPreparationId, steps[1].id);
expect(steps[1].nextPreparationId, isNull);
},
);

test('ScheduleFormUpdated preserves custom preparation step IDs', () async {
getScheduleByIdUseCase = StubGetScheduleByIdUseCase(
(_) async =>
schedule.copyWith(preparationMode: SchedulePreparationMode.custom),
);

PreparationEntity? updatedPreparation;
updatePreparationByScheduleIdUseCase =
StubUpdatePreparationByScheduleIdUseCase((preparation, _) async {
updatedPreparation = preparation;
});

final bloc = buildBloc();
addTearDown(bloc.close);

final editReady = bloc.stream.firstWhere(
(state) => state.status == ScheduleFormStatus.success,
);
bloc.add(const ScheduleFormEditRequested(scheduleId: 'schedule-1'));
await editReady;

const editedPreparation = PreparationEntity(
preparationStepList: [
PreparationStepEntity(
id: 'custom-step-1',
preparationName: 'Makeup',
preparationTime: Duration(minutes: 20),
nextPreparationId: 'custom-step-2',
),
PreparationStepEntity(
id: 'custom-step-2',
preparationName: 'Bathroom',
preparationTime: Duration(minutes: 5),
),
],
);
bloc.add(
const ScheduleFormPreparationChanged(preparation: editedPreparation),
);

final submitDone = bloc.stream.firstWhere(
(state) => state.submissionStatus == ScheduleFormSubmissionStatus.success,
);
bloc.add(const ScheduleFormUpdated());
await submitDone;

final steps = updatedPreparation!.preparationStepList;
expect(steps.map((step) => step.id), ['custom-step-1', 'custom-step-2']);
expect(steps[0].nextPreparationId, 'custom-step-2');
expect(steps[1].nextPreparationId, isNull);
});

test(
'ScheduleFormCreated persists custom preparation when changed',
() async {
Expand Down
Loading