Skip to content

Commit

Permalink
Merge branch 'main' into inputs-team-testing
Browse files Browse the repository at this point in the history
# Conflicts:
#	das_client/integration_test/test/train_journey_table_test.dart
#	das_client/lib/sfera/src/mapper/sfera_model_mapper.dart
  • Loading branch information
rawi-coding committed Jan 20, 2025
2 parents e596af1 + 17511ff commit 9969fc0
Show file tree
Hide file tree
Showing 39 changed files with 603 additions and 136 deletions.
18 changes: 16 additions & 2 deletions das_client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,23 @@ fvm flutter test --flavor dev --dart-define=MQTT_USERNAME=${MQTT_USERNAME} --dar

## Architecture

TODO

### Test file structure
To prevent confusion, fictive train numbers with the prefix `T` are used for the test scenarios. It is desired to create new train journeys for different features.
The file structure in [test_resources](test_resources) for a test scenario looks as follows:
* base directory named `<train number>_<optional description>`
* journey profile named `SFERA_JP_<train number>_<optional postfix>`
* corresponding segment profiles named `SFERA_SP_<train number>_<segment number>`
* corresponding train characteristics named `SFERA_TC_<train number>_<tc number>`
An example test scenario for train number T1 could look like this:
* T1_demo_journey/
* SFERA_JP_T1
* SFERA_JP_T1_without_stop
* SFERA_SP_T1_1
* SFERA_SP_T1_2
* SFERA_TC_T1_1
<a name="localization"></a>


## Localization

The app is available in three languages:
Expand Down
120 changes: 120 additions & 0 deletions das_client/integration_test/test/train_journey_table_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'dart:async';

import 'package:das_client/app/pages/journey/train_journey/widgets/header/header.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/table/additional_speed_restriction_row.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/table/balise_row.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart';
Expand All @@ -23,6 +26,58 @@ import '../util/test_utils.dart';

void main() {
group('train journey table test', () {
testWidgets('check if update sent is correct', (tester) async {
// Load app widget.
await prepareAndStartApp(tester);

// Select the correct train number
final trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description);
expect(trainNumberText, findsOneWidget);

await enterText(tester, trainNumberText, 'T9999');

// Log into the journey
final primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first;
await tester.tap(primaryButton);

// Wait for train journey to load
await tester.pumpAndSettle();

// Find the header and check if it is existent
final headerFinder = find.byType(Header);
expect(headerFinder, findsOneWidget);

// Timer logic: increase timer every second, rerun the base every 100 ms and check if the UI changed
int counter = 0;

final completer = Completer<void>();

expect(find.descendant(of: headerFinder, matching: find.text('+00:00')), findsOneWidget);

while (!completer.isCompleted) {
await tester.pumpAndSettle();

if (!find.descendant(of: headerFinder, matching: find.text('+00:00')).evaluate().isNotEmpty) {
expect(find.descendant(of: headerFinder, matching: find.text('+00:30')), findsOneWidget);
completer.complete();
break;
}

// cancel after 10 seconds
if (counter++ > 10 * 10) {
completer
.completeError(Exception('UI did not change from the base value to the updated value (+00:00 -> +00:30)'));
break;
}

await Future.delayed(const Duration(milliseconds: 100));
}

await completer.future;

await tester.pumpAndSettle();
});

testWidgets('test balise multiple level crossings', (tester) async {
await prepareAndStartApp(tester);

Expand Down Expand Up @@ -850,6 +905,71 @@ void main() {
final oltenOutgoingSpeeds =
find.descendant(of: oltenStationRow, matching: find.byKey(GraduatedSpeedsCellBody.outgoingSpeedsKey));
expect(oltenOutgoingSpeeds, findsNothing);

});

testWidgets('find base value when no punctuality update comes', (tester) async {
// Load app widget.
await prepareAndStartApp(tester);

final trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description);
expect(trainNumberText, findsOneWidget);

await enterText(tester, trainNumberText, 'T6');

final primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first;
await tester.tap(primaryButton);

// wait for train journey to load
await tester.pumpAndSettle();

//find the header and check if it is existent
final headerFinder = find.byType(Header);
expect(headerFinder, findsOneWidget);

//Find the text in the header
expect(find.descendant(of: headerFinder, matching: find.text('+00:00')), findsOneWidget);

await tester.pumpAndSettle();
});

testWidgets('check if the displayed current time is correct', (tester) async {
// Load app widget.
await prepareAndStartApp(tester);

//Select the correct train number
final trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description);
expect(trainNumberText, findsOneWidget);

await enterText(tester, trainNumberText, 'T6');

//Log into the journey
final primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first;
await tester.tap(primaryButton);

// wait for train journey to load
await tester.pumpAndSettle();

//find the header and check if it is existent
final headerFinder = find.byType(Header);
expect(headerFinder, findsOneWidget);

final DateTime currentTime = DateTime.now();
final String currentHour = currentTime.hour <= 9 ? '0${currentTime.hour}' : (currentTime.hour).toString();
final String currentMinutes = currentTime.minute <= 9 ? '0${currentTime.minute}' : (currentTime.minute).toString();
final String currentSeconds = currentTime.second <= 9 ? '0${currentTime.second}' : (currentTime.second).toString();
final String nextSecond =
currentTime.second <= 9 ? '0${currentTime.second + 1}' : (currentTime.second + 1).toString();
final String currentWholeTime = '$currentHour:$currentMinutes:$currentSeconds';
final String nextSecondWholeTime = '$currentHour:$currentMinutes:$nextSecond';

if (!find.descendant(of: headerFinder, matching: find.text(currentWholeTime)).evaluate().isNotEmpty) {
expect(find.descendant(of: headerFinder, matching: find.text(nextSecondWholeTime)), findsOneWidget);
} else {
expect(find.descendant(of: headerFinder, matching: find.text(currentWholeTime)), findsOneWidget);
}

await tester.pumpAndSettle();
});
});
}
Expand Down
Empty file.
6 changes: 2 additions & 4 deletions das_client/integration_test/test/train_search_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import '../util/test_utils.dart';

void main() {
group('train search screen tests', () {

testWidgets('test default values', (tester) async {
// Load app widget.
await prepareAndStartApp(tester);
Expand Down Expand Up @@ -59,7 +58,6 @@ void main() {
// check that the primary button is disabled
final primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first;
expect(tester.widget<SBBPrimaryButton>(primaryButton).onPressed, isNull);

});

testWidgets('test can select yesterday', (tester) async {
Expand Down Expand Up @@ -116,7 +114,8 @@ void main() {
final sbbDatePickerFinder = find.byWidgetPredicate((widget) => widget is SBBDatePicker);
final yesterdayFinder = find.descendant(
of: sbbDatePickerFinder,
matching: find.byWidgetPredicate((widget) => widget is Text && widget.data == '${(dayBeforeYesterday.day)}.'));
matching:
find.byWidgetPredicate((widget) => widget is Text && widget.data == '${(dayBeforeYesterday.day)}.'));
await tapElement(tester, yesterdayFinder);

// tap outside dialog
Expand Down Expand Up @@ -154,6 +153,5 @@ void main() {
expect(find.text('${l10n.c_error_code}: ${ErrorCode.sferaJpUnavailable.code}'), findsOneWidget);
expect(find.text(l10n.c_error_sfera_jp_unavailable), findsOneWidget);
});

});
}
2 changes: 1 addition & 1 deletion das_client/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: d9dad56c0cd0b4fd8b4fe3034a53fd42a0b990f6

COCOAPODS: 1.16.1
COCOAPODS: 1.16.2
2 changes: 1 addition & 1 deletion das_client/l10n/strings_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"c_error_sfera_handshake_rejected": "Server hat die Verbindung abgelehnt",
"c_error_sfera_request_timeout": "Timeout bei der Anfrage",
"c_error_sfera_jp_unavailable": "Fahrordnung nicht vorhanden",
"c_error_sfera_sp_invalid": "Unvollständige Daten erhalten",
"c_error_sfera_invalid": "Unvollständige Daten erhalten",
"c_connection_track_weiche": "Weiche",
"c_button_confirm": "Übernehmen"
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'package:das_client/app/bloc/train_journey_cubit.dart';
import 'package:das_client/app/widgets/das_text_styles.dart';
import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart';
import 'package:flutter/material.dart';
import 'package:das_client/model/journey/journey.dart';
import 'package:intl/intl.dart';

class TimeContainer extends StatelessWidget {
const TimeContainer({super.key});
Expand All @@ -21,21 +24,67 @@ class TimeContainer extends StatelessWidget {
height: 112.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('05:43:00', style: DASTextStyles.xLargeBold),
Flexible(child: _currentTime()),
_divider(),
Text('+00:01:30', style: DASTextStyles.xLargeLight),
Flexible(child: _punctualityDisplay(context)),
],
),
),
);
}
}

Widget _divider() {
return const Padding(
padding: EdgeInsets.symmetric(vertical: sbbDefaultSpacing * 0.5),
child: Divider(height: 1.0, color: SBBColors.cloud),
);
}
Widget _divider() {
return const Padding(
padding: EdgeInsets.symmetric(vertical: sbbDefaultSpacing * 0.5),
child: Divider(height: 1.0, color: SBBColors.cloud),
);
}

Widget _punctualityDisplay(BuildContext context) {
final bloc = context.trainJourneyCubit;

return StreamBuilder<Journey?>(
stream: bloc.journeyStream,
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == null || snapshot.data!.metadata.delay == null) {
return Padding(
padding:
const EdgeInsets.fromLTRB(sbbDefaultSpacing * 0.5, 0.0, sbbDefaultSpacing * 0.5, sbbDefaultSpacing * 0.5),
child: Text('+00:00', style: DASTextStyles.xLargeLight),
);
}

final Journey journey = snapshot.data!;
final Duration delay = journey.metadata.delay!;

final String minutes = NumberFormat('00').format(delay.inMinutes.abs() % 60);
final String seconds = NumberFormat('00').format(delay.inSeconds.abs() % 60);
final String formattedDuration = '${delay.isNegative ? '-' : '+'}$minutes:$seconds';

return Padding(
padding:
const EdgeInsets.fromLTRB(sbbDefaultSpacing * 0.5, 0.0, sbbDefaultSpacing * 0.5, sbbDefaultSpacing * 0.5),
child: Text(formattedDuration, style: DASTextStyles.xLargeLight),
);
},
);
}

StreamBuilder _currentTime() {
return StreamBuilder(
stream: Stream.periodic(const Duration(milliseconds: 200)),
builder: (context, snapshot) {
return Padding(
padding:
const EdgeInsets.fromLTRB(sbbDefaultSpacing * 0.5, sbbDefaultSpacing * 0.5, sbbDefaultSpacing * 0.5, 0),
child: Text(
DateFormat('HH:mm:ss').format(DateTime.now().toLocal()),
style: DASTextStyles.xLargeBold,
),
);
},
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class _TrainSelectionState extends State<TrainSelection> {
onChanged: (value) => context.trainJourneyCubit.updateTrainNumber(value),
controller: _trainNumberController,
labelText: context.l10n.p_train_selection_trainnumber_description,
keyboardType: TextInputType.number,
keyboardType: TextInputType.text,
),
);
}
Expand Down
2 changes: 2 additions & 0 deletions das_client/lib/model/journey/metadata.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class Metadata {
this.currentPosition,
this.routeStart,
this.routeEnd,
this.delay,
this.breakSeries,
this.additionalSpeedRestrictions = const [],
this.nonStandardTrackEquipmentSegments = const [],
Expand All @@ -23,6 +24,7 @@ class Metadata {
final List<AdditionalSpeedRestriction> additionalSpeedRestrictions;
final BaseData? routeStart;
final BaseData? routeEnd;
final Duration? delay;
final List<NonStandardTrackEquipmentSegment> nonStandardTrackEquipmentSegments;
final List<BracketStationSegment> bracketStationSegments;
final BreakSeries? breakSeries;
Expand Down
19 changes: 12 additions & 7 deletions das_client/lib/sfera/src/mapper/sfera_model_mapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import 'package:das_client/sfera/src/model/graduated_speed_info.dart';
import 'package:das_client/sfera/src/model/journey_profile.dart';
import 'package:das_client/sfera/src/model/multilingual_text.dart';
import 'package:das_client/sfera/src/model/network_specific_parameter.dart';
import 'package:das_client/sfera/src/model/related_train_information.dart';
import 'package:das_client/sfera/src/model/segment_profile.dart';
import 'package:das_client/sfera/src/model/taf_tap_location.dart';
import 'package:das_client/sfera/src/model/train_characteristics.dart';
Expand All @@ -48,18 +49,21 @@ class SferaModelMapper {
static const String _protectionSectionNspFacultativeName = 'facultative';
static const String _protectionSectionNspLengthTypeName = 'lengthType';

static Journey mapToJourney(JourneyProfile journeyProfile, List<SegmentProfile> segmentProfiles,
List<TrainCharacteristics> trainCharacteristics) {
static Journey mapToJourney(
{required JourneyProfile journeyProfile,
List<SegmentProfile> segmentProfiles = const [],
List<TrainCharacteristics> trainCharacteristics = const [],
RelatedTrainInformation? relatedTrainInformation}) {
try {
return _mapToJourney(journeyProfile, segmentProfiles, trainCharacteristics);
return _mapToJourney(journeyProfile, segmentProfiles, trainCharacteristics, relatedTrainInformation);
} catch (e, s) {
Fimber.e('Error mapping journey-/segment profiles to journey:', ex: e, stacktrace: s);
return Journey.invalid();
}
}

static Journey _mapToJourney(JourneyProfile journeyProfile, List<SegmentProfile> segmentProfiles,
List<TrainCharacteristics> trainCharacteristics) {
List<TrainCharacteristics> trainCharacteristics, RelatedTrainInformation? relatedTrainInformation) {
final journeyData = <BaseData>[];

final segmentProfilesLists = journeyProfile.segmentProfilesLists.toList();
Expand Down Expand Up @@ -159,14 +163,15 @@ class SferaModelMapper {
additionalSpeedRestrictions: additionalSpeedRestrictions,
routeStart: journeyData.firstOrNull,
routeEnd: journeyData.lastOrNull,
delay: relatedTrainInformation?.ownTrain.trainLocationInformation.delay.delayAsDuration,
nonStandardTrackEquipmentSegments: trackEquipmentSegments,
bracketStationSegments: _parseBracketStationSegments(servicePoints),
availableBreakSeries: _parseAvailableBreakSeries(journeyData),
breakSeries: trainCharacteristic?.tcFeatures.trainCategoryCode != null &&
trainCharacteristic?.tcFeatures.brakedWeightPercentage != null
trainCharacteristic?.tcFeatures.brakedWeightPercentage != null
? BreakSeries(
trainSeries: trainCharacteristic!.tcFeatures.trainCategoryCode!,
breakSeries: trainCharacteristic.tcFeatures.brakedWeightPercentage!)
trainSeries: trainCharacteristic!.tcFeatures.trainCategoryCode!,
breakSeries: trainCharacteristic.tcFeatures.brakedWeightPercentage!)
: null,
),
data: journeyData,
Expand Down
Loading

0 comments on commit 9969fc0

Please sign in to comment.