Skip to content

Commit 1e4c412

Browse files
committed
fix: copy default preparation steps for schedule edits
1 parent eabe037 commit 1e4c412

3 files changed

Lines changed: 165 additions & 18 deletions

File tree

lib/presentation/schedule_create/bloc/schedule_form_bloc.dart

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import 'package:injectable/injectable.dart';
44
import 'package:on_time_front/core/dio/api_error_message.dart';
55
import 'package:on_time_front/domain/entities/place_entity.dart';
66
import 'package:on_time_front/domain/entities/preparation_entity.dart';
7+
import 'package:on_time_front/domain/entities/preparation_step_entity.dart';
78
import 'package:on_time_front/domain/entities/schedule_entity.dart';
9+
import 'package:on_time_front/domain/entities/schedule_preparation_mode.dart';
810
import 'package:on_time_front/domain/use-cases/create_custom_preparation_use_case.dart';
911
import 'package:on_time_front/domain/use-cases/create_schedule_with_place_use_case.dart';
1012
import 'package:on_time_front/domain/use-cases/get_default_preparation_use_case.dart';
@@ -97,6 +99,7 @@ class ScheduleFormBloc extends Bloc<ScheduleFormEvent, ScheduleFormState> {
9799
scheduleSpareTime: scheduleEntity.scheduleSpareTime,
98100
scheduleNote: scheduleEntity.scheduleNote,
99101
preparation: preparationEntity,
102+
originalPreparationMode: scheduleEntity.preparationMode,
100103
),
101104
);
102105
}
@@ -224,7 +227,7 @@ class ScheduleFormBloc extends Bloc<ScheduleFormEvent, ScheduleFormState> {
224227
await _updateScheduleUseCase(scheduleEntity);
225228
if (state.isChanged != IsPreparationChanged.unchanged) {
226229
await _updatePreparationByScheduleIdUseCase(
227-
state.preparation!,
230+
_preparationForScheduleUpdate(),
228231
scheduleEntity.id,
229232
);
230233
}
@@ -311,6 +314,28 @@ class ScheduleFormBloc extends Bloc<ScheduleFormEvent, ScheduleFormState> {
311314
);
312315
}
313316

317+
PreparationEntity _preparationForScheduleUpdate() {
318+
final preparation = state.preparation!;
319+
if (state.originalPreparationMode !=
320+
SchedulePreparationMode.defaultPreparation) {
321+
return preparation;
322+
}
323+
324+
final orderedSteps = preparation.ordered.preparationStepList;
325+
final newIds = List.generate(orderedSteps.length, (_) => Uuid().v7());
326+
final copiedSteps = <PreparationStepEntity>[
327+
for (var i = 0; i < orderedSteps.length; i++)
328+
PreparationStepEntity(
329+
id: newIds[i],
330+
preparationName: orderedSteps[i].preparationName,
331+
preparationTime: orderedSteps[i].preparationTime,
332+
nextPreparationId: i + 1 < newIds.length ? newIds[i + 1] : null,
333+
),
334+
];
335+
336+
return PreparationEntity(preparationStepList: copiedSteps);
337+
}
338+
314339
Future<void> _trackScheduleCreated(ScheduleEntity scheduleEntity) async {
315340
await _productUsageEventTracker.track(
316341
ProductUsageEvent(

lib/presentation/schedule_create/bloc/schedule_form_state.dart

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ final class ScheduleFormState extends Equatable {
2222
final Duration? scheduleSpareTime;
2323
final String? scheduleNote;
2424
final PreparationEntity? preparation;
25+
final SchedulePreparationMode? originalPreparationMode;
2526
final bool isValid;
2627
final Duration? maxAvailableTime;
2728
final String? previousScheduleName;
@@ -40,6 +41,7 @@ final class ScheduleFormState extends Equatable {
4041
this.scheduleSpareTime,
4142
this.scheduleNote,
4243
this.preparation,
44+
this.originalPreparationMode,
4345
this.isValid = false,
4446
this.maxAvailableTime,
4547
this.previousScheduleName,
@@ -59,6 +61,7 @@ final class ScheduleFormState extends Equatable {
5961
Duration? scheduleSpareTime,
6062
String? scheduleNote,
6163
PreparationEntity? preparation,
64+
SchedulePreparationMode? originalPreparationMode,
6265
bool? isValid,
6366
Duration? maxAvailableTime,
6467
String? previousScheduleName,
@@ -79,6 +82,8 @@ final class ScheduleFormState extends Equatable {
7982
scheduleSpareTime: scheduleSpareTime ?? this.scheduleSpareTime,
8083
scheduleNote: scheduleNote ?? this.scheduleNote,
8184
preparation: preparation ?? this.preparation,
85+
originalPreparationMode:
86+
originalPreparationMode ?? this.originalPreparationMode,
8287
isValid: isValid ?? this.isValid,
8388
maxAvailableTime: maxAvailableTime ?? this.maxAvailableTime,
8489
previousScheduleName: previousScheduleName ?? this.previousScheduleName,
@@ -111,21 +116,22 @@ final class ScheduleFormState extends Equatable {
111116

112117
@override
113118
List<Object?> get props => [
114-
status,
115-
submissionStatus,
116-
submissionError,
117-
id,
118-
placeId,
119-
placeName,
120-
scheduleName,
121-
scheduleTime,
122-
moveTime,
123-
isChanged,
124-
scheduleSpareTime,
125-
scheduleNote,
126-
preparation,
127-
isValid,
128-
maxAvailableTime,
129-
previousScheduleName,
130-
];
119+
status,
120+
submissionStatus,
121+
submissionError,
122+
id,
123+
placeId,
124+
placeName,
125+
scheduleName,
126+
scheduleTime,
127+
moveTime,
128+
isChanged,
129+
scheduleSpareTime,
130+
scheduleNote,
131+
preparation,
132+
originalPreparationMode,
133+
isValid,
134+
maxAvailableTime,
135+
previousScheduleName,
136+
];
131137
}

test/presentation/schedule_create/bloc/schedule_form_bloc_test.dart

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:on_time_front/domain/entities/preparation_entity.dart';
55
import 'package:on_time_front/domain/entities/preparation_step_entity.dart';
66
import 'package:on_time_front/domain/entities/product_usage_event.dart';
77
import 'package:on_time_front/domain/entities/schedule_entity.dart';
8+
import 'package:on_time_front/domain/entities/schedule_preparation_mode.dart';
89
import 'package:on_time_front/domain/entities/user_entity.dart';
910
import 'package:on_time_front/domain/use-cases/track_product_usage_event_use_case.dart';
1011
import 'package:on_time_front/domain/use-cases/create_custom_preparation_use_case.dart';
@@ -477,6 +478,121 @@ void main() {
477478
expect(preparationUpdateCount, 0);
478479
});
479480

481+
test(
482+
'ScheduleFormUpdated copies default preparation with fresh step IDs',
483+
() async {
484+
getScheduleByIdUseCase = StubGetScheduleByIdUseCase(
485+
(_) async => schedule.copyWith(
486+
preparationMode: SchedulePreparationMode.defaultPreparation,
487+
),
488+
);
489+
490+
PreparationEntity? updatedPreparation;
491+
updatePreparationByScheduleIdUseCase =
492+
StubUpdatePreparationByScheduleIdUseCase((preparation, _) async {
493+
updatedPreparation = preparation;
494+
});
495+
496+
final bloc = buildBloc();
497+
addTearDown(bloc.close);
498+
499+
final editReady = bloc.stream.firstWhere(
500+
(state) => state.status == ScheduleFormStatus.success,
501+
);
502+
bloc.add(const ScheduleFormEditRequested(scheduleId: 'schedule-1'));
503+
await editReady;
504+
505+
const editedPreparation = PreparationEntity(
506+
preparationStepList: [
507+
PreparationStepEntity(
508+
id: 'default-step-1',
509+
preparationName: 'Makeup',
510+
preparationTime: Duration(minutes: 20),
511+
nextPreparationId: 'default-step-2',
512+
),
513+
PreparationStepEntity(
514+
id: 'default-step-2',
515+
preparationName: 'Bathroom',
516+
preparationTime: Duration(minutes: 5),
517+
),
518+
],
519+
);
520+
bloc.add(
521+
const ScheduleFormPreparationChanged(preparation: editedPreparation),
522+
);
523+
524+
final submitDone = bloc.stream.firstWhere(
525+
(state) =>
526+
state.submissionStatus == ScheduleFormSubmissionStatus.success,
527+
);
528+
bloc.add(const ScheduleFormUpdated());
529+
await submitDone;
530+
531+
final steps = updatedPreparation!.preparationStepList;
532+
expect(steps, hasLength(2));
533+
expect(steps[0].preparationName, 'Makeup');
534+
expect(steps[0].preparationTime, const Duration(minutes: 20));
535+
expect(steps[1].preparationName, 'Bathroom');
536+
expect(steps[1].preparationTime, const Duration(minutes: 5));
537+
expect(steps[0].id, isNot('default-step-1'));
538+
expect(steps[1].id, isNot('default-step-2'));
539+
expect(steps[0].nextPreparationId, steps[1].id);
540+
expect(steps[1].nextPreparationId, isNull);
541+
},
542+
);
543+
544+
test('ScheduleFormUpdated preserves custom preparation step IDs', () async {
545+
getScheduleByIdUseCase = StubGetScheduleByIdUseCase(
546+
(_) async =>
547+
schedule.copyWith(preparationMode: SchedulePreparationMode.custom),
548+
);
549+
550+
PreparationEntity? updatedPreparation;
551+
updatePreparationByScheduleIdUseCase =
552+
StubUpdatePreparationByScheduleIdUseCase((preparation, _) async {
553+
updatedPreparation = preparation;
554+
});
555+
556+
final bloc = buildBloc();
557+
addTearDown(bloc.close);
558+
559+
final editReady = bloc.stream.firstWhere(
560+
(state) => state.status == ScheduleFormStatus.success,
561+
);
562+
bloc.add(const ScheduleFormEditRequested(scheduleId: 'schedule-1'));
563+
await editReady;
564+
565+
const editedPreparation = PreparationEntity(
566+
preparationStepList: [
567+
PreparationStepEntity(
568+
id: 'custom-step-1',
569+
preparationName: 'Makeup',
570+
preparationTime: Duration(minutes: 20),
571+
nextPreparationId: 'custom-step-2',
572+
),
573+
PreparationStepEntity(
574+
id: 'custom-step-2',
575+
preparationName: 'Bathroom',
576+
preparationTime: Duration(minutes: 5),
577+
),
578+
],
579+
);
580+
bloc.add(
581+
const ScheduleFormPreparationChanged(preparation: editedPreparation),
582+
);
583+
584+
final submitDone = bloc.stream.firstWhere(
585+
(state) => state.submissionStatus == ScheduleFormSubmissionStatus.success,
586+
);
587+
bloc.add(const ScheduleFormUpdated());
588+
await submitDone;
589+
590+
final steps = updatedPreparation!.preparationStepList;
591+
expect(steps.map((step) => step.id), ['custom-step-1', 'custom-step-2']);
592+
expect(steps[0].nextPreparationId, 'custom-step-2');
593+
expect(steps[1].nextPreparationId, isNull);
594+
});
595+
480596
test(
481597
'ScheduleFormCreated persists custom preparation when changed',
482598
() async {

0 commit comments

Comments
 (0)