Bearbeiten und Löschen von Gruppen #148
@@ -11,4 +11,8 @@ linter:
|
||||
prefer_const_literals_to_create_immutables: true
|
||||
unnecessary_const: true
|
||||
lines_longer_than_80_chars: false
|
||||
constant_identifier_names: false
|
||||
constant_identifier_names: false
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/db/tables/group_table.dart';
|
||||
import 'package:tallee/data/db/tables/match_table.dart';
|
||||
import 'package:tallee/data/db/tables/player_group_table.dart';
|
||||
import 'package:tallee/data/dto/group.dart';
|
||||
import 'package:tallee/data/dto/player.dart';
|
||||
|
||||
part 'group_dao.g.dart';
|
||||
|
||||
@DriftAccessor(tables: [GroupTable, PlayerGroupTable])
|
||||
@DriftAccessor(tables: [GroupTable, PlayerGroupTable, MatchTable])
|
||||
class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
GroupDao(super.db);
|
||||
|
||||
@@ -205,8 +206,6 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Retrieves the number of groups in the database.
|
||||
Future<int> getGroupCount() async {
|
||||
final count =
|
||||
@@ -235,10 +234,13 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
/// Replaces all players in a group with the provided list of players.
|
||||
/// Removes all existing players from the group and adds the new players.
|
||||
/// Also adds any new players to the player table if they don't exist.
|
||||
Future<void> replaceGroupPlayers({
|
||||
/// Returns `true` if the group exists and players were replaced, `false` otherwise.
|
||||
Future<bool> replaceGroupPlayers({
|
||||
required String groupId,
|
||||
required List<Player> newPlayers,
|
||||
}) async {
|
||||
if (!await groupExists(groupId: groupId)) return false;
|
||||
|
||||
await db.transaction(() async {
|
||||
// Remove all existing players from the group
|
||||
final deleteQuery = delete(db.playerGroupTable)
|
||||
@@ -270,5 +272,6 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
),
|
||||
);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,4 +8,6 @@ mixin _$GroupDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||
$PlayerGroupTableTable get playerGroupTable =>
|
||||
attachedDatabase.playerGroupTable;
|
||||
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
||||
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||
}
|
||||
|
||||
@@ -268,6 +268,34 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||
return count ?? 0;
|
||||
}
|
||||
|
||||
/// Retrieves all matches associated with the given [groupId].
|
||||
/// Queries the database directly, filtering by [groupId].
|
||||
Future<List<Match>> getGroupMatches({required String groupId}) async {
|
||||
final query = select(matchTable)..where((m) => m.groupId.equals(groupId));
|
||||
final rows = await query.get();
|
||||
|
||||
return Future.wait(
|
||||
rows.map((row) async {
|
||||
final game = await db.gameDao.getGameById(gameId: row.gameId);
|
||||
final group = await db.groupDao.getGroupById(groupId: groupId);
|
||||
final players =
|
||||
await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? [];
|
||||
final winner = await db.matchDao.getWinner(matchId: row.id);
|
||||
return Match(
|
||||
id: row.id,
|
||||
name: row.name ?? '',
|
||||
game: game,
|
||||
group: group,
|
||||
players: players,
|
||||
notes: row.notes ?? '',
|
||||
createdAt: row.createdAt,
|
||||
endedAt: row.endedAt,
|
||||
winner: winner,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/// Checks if a match with the given [matchId] exists in the database.
|
||||
/// Returns `true` if the match exists, otherwise `false`.
|
||||
Future<bool> matchExists({required String matchId}) async {
|
||||
@@ -338,6 +366,17 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
|
||||
/// Removes the group association of the match with the given [matchId].
|
||||
/// Sets the groupId to null.
|
||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||
Future<bool> removeMatchGroup({required String matchId}) async {
|
||||
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
||||
final rowsAffected = await query.write(
|
||||
const MatchTableCompanion(groupId: Value(null)),
|
||||
);
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
|
||||
/// Updates the createdAt timestamp of the match with the given [matchId].
|
||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||
Future<bool> updateMatchCreatedAt({
|
||||
|
||||
@@ -1123,7 +1123,7 @@ class $MatchTableTable extends MatchTable
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES group_table (id) ON DELETE CASCADE',
|
||||
'REFERENCES group_table (id) ON DELETE SET NULL',
|
||||
),
|
||||
);
|
||||
static const VerificationMeta _nameMeta = const VerificationMeta('name');
|
||||
@@ -2780,7 +2780,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
||||
'group_table',
|
||||
limitUpdateKind: UpdateKind.delete,
|
||||
),
|
||||
result: [TableUpdate('match_table', kind: UpdateKind.delete)],
|
||||
result: [TableUpdate('match_table', kind: UpdateKind.update)],
|
||||
),
|
||||
WritePropagation(
|
||||
on: TableUpdateQuery.onTableName(
|
||||
|
||||
@@ -7,13 +7,15 @@ class MatchTable extends Table {
|
||||
TextColumn get gameId =>
|
||||
text().references(GameTable, #id, onDelete: KeyAction.cascade)();
|
||||
// Nullable if there is no group associated with the match
|
||||
TextColumn get groupId =>
|
||||
text().references(GroupTable, #id, onDelete: KeyAction.cascade).nullable()();
|
||||
TextColumn get name => text().nullable()();
|
||||
// onDelete: If a group gets deleted, groupId in the match gets set to null
|
||||
TextColumn get groupId => text()
|
||||
.references(GroupTable, #id, onDelete: KeyAction.setNull)
|
||||
.nullable()();
|
||||
TextColumn get name => text()();
|
||||
TextColumn get notes => text().nullable()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
DateTimeColumn get endedAt => dateTime().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column<Object>> get primaryKey => {id};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/constants.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
@@ -11,11 +12,13 @@ import 'package:tallee/presentation/widgets/player_selection.dart';
|
||||
import 'package:tallee/presentation/widgets/text_input/text_input_field.dart';
|
||||
|
||||
class CreateGroupView extends StatefulWidget {
|
||||
const CreateGroupView({super.key, this.groupToEdit});
|
||||
const CreateGroupView({super.key, this.groupToEdit, this.onMembersChanged});
|
||||
|
||||
/// The group to edit, if any
|
||||
final Group? groupToEdit;
|
||||
|
||||
final VoidCallback? onMembersChanged;
|
||||
|
||||
@override
|
||||
State<CreateGroupView> createState() => _CreateGroupViewState();
|
||||
}
|
||||
@@ -69,49 +72,6 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
title: Text(
|
||||
widget.groupToEdit == null ? loc.create_new_group : loc.edit_group,
|
||||
),
|
||||
actions: widget.groupToEdit == null
|
||||
? []
|
||||
: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
if (widget.groupToEdit != null) {
|
||||
showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(loc.delete_group),
|
||||
content: Text(loc.this_cannot_be_undone),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pop(false),
|
||||
child: Text(loc.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pop(true),
|
||||
child: Text(loc.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
).then((confirmed) async {
|
||||
if (confirmed == true && context.mounted) {
|
||||
bool success = await db.groupDao.deleteGroup(
|
||||
groupId: widget.groupToEdit!.id,
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
if (success) {
|
||||
Navigator.pop(context);
|
||||
} else {
|
||||
if (!mounted) return;
|
||||
showSnackbar(message: loc.error_deleting_group);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
@@ -122,6 +82,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
child: TextInputField(
|
||||
controller: _groupNameController,
|
||||
hintText: loc.group_name,
|
||||
maxLength: Constants.MAX_GROUP_NAME_LENGTH,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
@@ -144,42 +105,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
(_groupNameController.text.isEmpty ||
|
||||
(selectedPlayers.length < 2))
|
||||
? null
|
||||
: () async {
|
||||
late Group? updatedGroup;
|
||||
late bool success;
|
||||
if (widget.groupToEdit == null) {
|
||||
success = await db.groupDao.addGroup(
|
||||
group: Group(
|
||||
name: _groupNameController.text.trim(),
|
||||
members: selectedPlayers,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
updatedGroup = Group(
|
||||
id: widget.groupToEdit!.id,
|
||||
name: _groupNameController.text.trim(),
|
||||
description: '',
|
||||
members: selectedPlayers,
|
||||
);
|
||||
//TODO: Implement group editing in database
|
||||
/*
|
||||
success = await db.groupDao.updateGroup(
|
||||
group: updatedGroup,
|
||||
);
|
||||
*/
|
||||
success = true;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
if (success) {
|
||||
Navigator.pop(context, updatedGroup);
|
||||
} else {
|
||||
showSnackbar(
|
||||
message: widget.groupToEdit == null
|
||||
? loc.error_creating_group
|
||||
: loc.error_editing_group,
|
||||
);
|
||||
}
|
||||
},
|
||||
: _saveGroup,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
@@ -189,6 +115,104 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Saves the group by creating a new one or updating the existing one,
|
||||
/// depending on whether the widget is in edit mode.
|
||||
Future<void> _saveGroup() async {
|
||||
final loc = AppLocalizations.of(context);
|
||||
late bool success;
|
||||
Group? updatedGroup;
|
||||
|
||||
if (widget.groupToEdit == null) {
|
||||
success = await _createGroup();
|
||||
} else {
|
||||
final result = await _editGroup();
|
||||
success = result.$1;
|
||||
updatedGroup = result.$2;
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
if (success) {
|
||||
Navigator.pop(context, updatedGroup);
|
||||
} else {
|
||||
showSnackbar(
|
||||
message: widget.groupToEdit == null
|
||||
? loc.error_creating_group
|
||||
: loc.error_editing_group,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles creating a new group and returns whether the operation was successful.
|
||||
Future<bool> _createGroup() async {
|
||||
final groupName = _groupNameController.text.trim();
|
||||
|
||||
final success = await db.groupDao.addGroup(
|
||||
|
sneeex marked this conversation as resolved
Outdated
|
||||
group: Group(name: groupName, description: '', members: selectedPlayers),
|
||||
);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// Handles editing an existing group and returns a tuple of
|
||||
/// (success, updatedGroup).
|
||||
Future<(bool, Group?)> _editGroup() async {
|
||||
final groupName = _groupNameController.text.trim();
|
||||
|
||||
|
sneeex marked this conversation as resolved
Outdated
flixcoo
commented
gerne die Methode nochmal aufteilen, wie bei mir in gerne die Methode nochmal aufteilen, wie bei mir in `CreateMatchView`:
```dart
void buttonNavigation(BuildContext context) async {
if (widget.matchToEdit != null) {
await updateMatch();
if (context.mounted) {
Navigator.pop(context);
}
} else {
final match = await createMatch();
if (context.mounted) {
Navigator.pushReplacement(
context,
adaptivePageRoute(
fullscreenDialog: true,
builder: (context) => MatchResultView(
match: match,
onWinnerChanged: widget.onWinnerChanged,
),
),
);
}
}
}
```
sneeex
commented
nö nö
|
||||
Group? updatedGroup = Group(
|
||||
id: widget.groupToEdit!.id,
|
||||
name: groupName,
|
||||
description: '',
|
||||
members: selectedPlayers,
|
||||
);
|
||||
|
||||
bool successfullNameChange = true;
|
||||
bool successfullMemberChange = true;
|
||||
|
||||
if (widget.groupToEdit!.name != groupName) {
|
||||
successfullNameChange = await db.groupDao.updateGroupName(
|
||||
groupId: widget.groupToEdit!.id,
|
||||
newName: groupName,
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.groupToEdit!.members != selectedPlayers) {
|
||||
successfullMemberChange = await db.groupDao.replaceGroupPlayers(
|
||||
groupId: widget.groupToEdit!.id,
|
||||
newPlayers: selectedPlayers,
|
||||
);
|
||||
await deleteObsoleteMatchGroupRelations();
|
||||
widget.onMembersChanged?.call();
|
||||
}
|
||||
|
||||
final success = successfullNameChange && successfullMemberChange;
|
||||
|
||||
return (success, updatedGroup);
|
||||
}
|
||||
|
||||
/// Removes the group association from matches that no longer belong to the edited group.
|
||||
|
sneeex marked this conversation as resolved
Outdated
flixcoo
commented
Kurzen doc comment nochmal zu dieser Methode vllt Kurzen doc comment nochmal zu dieser Methode vllt
|
||||
///
|
||||
|
sneeex marked this conversation as resolved
Outdated
flixcoo
commented
Lieber Methode Lieber Methode `getMatchesToGroup()` in `groupDao.dart`, kann ggf. später auch noch einmal verwendet werden
sneeex
commented
hast das auch so gemacht hast das auch so gemacht
sneeex
commented
habs von dir kopiert habs von dir kopiert
flixcoo
commented
wo hab ich das gemacht? File und Zeile wo hab ich das gemacht? File und Zeile
sneeex
commented
flixcoo
commented
ja aber da diese vorgehensweise jetzt mehr als einmal im code ist machts erst recht sinn dafür ne methode zu schreiben und entsprechende vorkommen zu ersetzen ja aber da diese vorgehensweise jetzt mehr als einmal im code ist machts erst recht sinn dafür ne methode zu schreiben und entsprechende vorkommen zu ersetzen
|
||||
/// After updating the group's members, matches that were previously linked to
|
||||
/// this group but don't have any of the newly selected players are considered
|
||||
/// obsolete. For each such match, the group association is removed by setting
|
||||
/// its [groupId] to null.
|
||||
Future<void> deleteObsoleteMatchGroupRelations() async {
|
||||
final groupMatches = await db.matchDao.getGroupMatches(
|
||||
groupId: widget.groupToEdit!.id,
|
||||
);
|
||||
|
||||
final selectedPlayerIds = selectedPlayers.map((p) => p.id).toSet();
|
||||
final relationshipsToDelete = groupMatches.where((match) {
|
||||
return !match.players.any(
|
||||
(player) => selectedPlayerIds.contains(player.id),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
for (var match in relationshipsToDelete) {
|
||||
await db.matchDao.removeMatchGroup(matchId: match.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays a snackbar with the given message and optional action.
|
||||
///
|
||||
/// [message] The message to display in the snackbar.
|
||||
|
||||
@@ -191,7 +191,12 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
||||
context,
|
||||
adaptivePageRoute(
|
||||
builder: (context) {
|
||||
return CreateGroupView(groupToEdit: _group);
|
||||
return CreateGroupView(
|
||||
groupToEdit: _group,
|
||||
onMembersChanged: () {
|
||||
_loadStatistics();
|
||||
|
sneeex marked this conversation as resolved
flixcoo
commented
Warum wird Warum wird `isLoading` nicht in `_loadStatistics` auf true gesetzt?
sneeex
commented
weil du's so gemacht hast großer weil du's so gemacht hast großer
flixcoo
commented
Setzt es mal in der Methode anfangs auf true, so ists z.B. auch im Setzt es mal in der Methode anfangs auf true, so ists z.B. auch im `GroupView`
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -242,10 +247,8 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
||||
|
||||
/// Loads statistics for this group
|
||||
Future<void> _loadStatistics() async {
|
||||
final matches = await db.matchDao.getAllMatches();
|
||||
final groupMatches = matches
|
||||
.where((match) => match.group?.id == _group.id)
|
||||
.toList();
|
||||
isLoading = true;
|
||||
final groupMatches = await db.matchDao.getGroupMatches(groupId: _group.id);
|
||||
|
||||
setState(() {
|
||||
totalMatches = groupMatches.length;
|
||||
@@ -260,7 +263,9 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
||||
|
||||
// Count wins for each player
|
||||
for (var match in matches) {
|
||||
if (match.winner != null) {
|
||||
if (match.winner != null &&
|
||||
_group.members.any((m) => m.id == match.winner?.id)) {
|
||||
print(match.winner);
|
||||
bestPlayerCounts.update(
|
||||
match.winner!,
|
||||
(value) => value + 1,
|
||||
|
||||
@@ -363,6 +363,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
final match = widget.matchToEdit!;
|
||||
_matchNameController.text = match.name;
|
||||
selectedPlayers = match.players;
|
||||
selectedGameIndex = 0;
|
||||
|
||||
if (match.group != null) {
|
||||
selectedGroup = match.group;
|
||||
|
||||
@@ -31,9 +31,9 @@ dependencies:
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
build_runner: ^2.5.4
|
||||
build_runner: ^2.7.0
|
||||
dart_pubspec_licenses: ^3.0.14
|
||||
drift_dev: ^2.27.0
|
||||
drift_dev: ^2.29.0
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
flutter:
|
||||
|
||||
@@ -42,7 +42,7 @@ void main() {
|
||||
testPlayer4 = Player(name: 'Diana', description: '');
|
||||
testPlayer5 = Player(name: 'Eve', description: '');
|
||||
testGroup1 = Group(
|
||||
name: 'Test Group 2',
|
||||
name: 'Test Group 1',
|
||||
description: '',
|
||||
members: [testPlayer1, testPlayer2, testPlayer3],
|
||||
);
|
||||
@@ -307,5 +307,69 @@ void main() {
|
||||
expect(fetchedMatch.winner, isNotNull);
|
||||
expect(fetchedMatch.winner!.id, testPlayer5.id);
|
||||
});
|
||||
|
||||
test(
|
||||
|
sneeex marked this conversation as resolved
flixcoo
commented
Unnötiger Kommentar Unnötiger Kommentar
|
||||
'removeMatchGroup removes group from match with existing group',
|
||||
() async {
|
||||
await database.matchDao.addMatch(match: testMatch1);
|
||||
|
||||
final removed = await database.matchDao.removeMatchGroup(
|
||||
matchId: testMatch1.id,
|
||||
);
|
||||
expect(removed, isTrue);
|
||||
|
||||
final updatedMatch = await database.matchDao.getMatchById(
|
||||
matchId: testMatch1.id,
|
||||
);
|
||||
expect(updatedMatch.group, null);
|
||||
expect(updatedMatch.game.id, testMatch1.game.id);
|
||||
expect(updatedMatch.name, testMatch1.name);
|
||||
|
sneeex marked this conversation as resolved
flixcoo
commented
Unnötiger Kommentar & auf deutsch Unnötiger Kommentar & auf deutsch
|
||||
expect(updatedMatch.notes, testMatch1.notes);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'removeMatchGroup on match that already has no group still succeeds',
|
||||
() async {
|
||||
await database.matchDao.addMatch(match: testMatchOnlyPlayers);
|
||||
|
||||
final removed = await database.matchDao.removeMatchGroup(
|
||||
matchId: testMatchOnlyPlayers.id,
|
||||
);
|
||||
expect(removed, isTrue);
|
||||
|
||||
final updatedMatch = await database.matchDao.getMatchById(
|
||||
|
sneeex marked this conversation as resolved
flixcoo
commented
Unnötiger Kommentar & auf deutsch Unnötiger Kommentar & auf deutsch
|
||||
matchId: testMatchOnlyPlayers.id,
|
||||
);
|
||||
expect(updatedMatch.group, null);
|
||||
},
|
||||
);
|
||||
|
||||
test('removeMatchGroup on non-existing match returns false', () async {
|
||||
final removed = await database.matchDao.removeMatchGroup(
|
||||
matchId: 'non-existing-id',
|
||||
);
|
||||
expect(removed, isFalse);
|
||||
});
|
||||
|
||||
test('Fetching all matches related to a group', () async {
|
||||
var matches = await database.matchDao.getGroupMatches(
|
||||
groupId: 'non-existing-id',
|
||||
);
|
||||
|
||||
expect(matches, isEmpty);
|
||||
|
||||
await database.matchDao.addMatch(match: testMatch1);
|
||||
print(await database.matchDao.getAllMatches());
|
||||
|
||||
matches = await database.matchDao.getGroupMatches(groupId: testGroup1.id);
|
||||
|
||||
expect(matches, isNotEmpty);
|
||||
|
||||
final match = matches.first;
|
||||
expect(match.id, testMatch1.id);
|
||||
expect(match.group, isNotNull);
|
||||
expect(match.group!.id, testGroup1.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user
Kompletten Code hier in mindestens eine Methode auslagern, falls sinnvoll auch mehrere