diff --git a/lib/models/measurements/measurement_entry.dart b/lib/models/measurements/measurement_entry.dart index e46c87d3..bb795d4a 100644 --- a/lib/models/measurements/measurement_entry.dart +++ b/lib/models/measurements/measurement_entry.dart @@ -29,6 +29,15 @@ class MeasurementEntry extends Equatable { required this.notes, }); + MeasurementEntry copyWith({int? id, int? category, DateTime? date, num? value, String? notes}) => + MeasurementEntry( + id: id ?? this.id, + category: category ?? this.category, + date: date ?? this.date, + value: value ?? this.value, + notes: notes ?? this.notes, + ); + // Boilerplate factory MeasurementEntry.fromJson(Map json) => _$MeasurementEntryFromJson(json); diff --git a/lib/providers/measurement.dart b/lib/providers/measurement.dart index 70cc5bac..94e16f9e 100644 --- a/lib/providers/measurement.dart +++ b/lib/providers/measurement.dart @@ -87,7 +87,7 @@ class MeasurementProvider with ChangeNotifier { notifyListeners(); } - /// Adds a measurement category to the remote db + /// Adds a measurement category Future addCategory(MeasurementCategory category) async { Uri postUri = baseProvider.makeUrl(_categoryUrl); final Map newCategoryMap = await baseProvider.post(category.toJson(), postUri); @@ -97,7 +97,7 @@ class MeasurementProvider with ChangeNotifier { notifyListeners(); } - /// Deletes a measurement category from the remote db + /// Deletes a measurement category Future deleteCategory(int id) async { MeasurementCategory category = _categories.firstWhere((element) => element.id == id, orElse: () => throw NoSuchEntryException()); @@ -110,11 +110,27 @@ class MeasurementProvider with ChangeNotifier { if (response.statusCode >= 400) { _categories.insert(categoryIndex, category); notifyListeners(); - throw WgerHttpException(response.body); } } - /// Adds a measurement entry to the remote db + /// Edits a measurement category + /// Currently there isn't any fallback if the call to the api is unsuccessful, as WgerBaseProvider.patch only returns the response body and not the whole response + Future editCategory(int id, String? newName, String? newUnit) async { + final MeasurementCategory oldCategory = _categories.firstWhere((category) => category.id == id, + orElse: () => throw NoSuchEntryException()); + int categoryIndex = _categories.indexOf(oldCategory); + _categories.removeAt(categoryIndex); + final MeasurementCategory tempNewCategory = oldCategory.copyWith(name: newName, unit: newUnit); + + final Map response = + await baseProvider.patch(tempNewCategory.toJson(), baseProvider.makeUrl(_categoryUrl)); + final MeasurementCategory newCategory = + (MeasurementCategory.fromJson(response)).copyWith(entries: oldCategory.entries); + _categories.insert(categoryIndex, newCategory); + notifyListeners(); + } + + /// Adds a measurement entry Future addEntry(MeasurementEntry entry) async { Uri postUri = baseProvider.makeUrl(_entryUrl); @@ -132,7 +148,7 @@ class MeasurementProvider with ChangeNotifier { }, orElse: () => throw NoSuchEntryException()); } - /// Deletes a measurement entry from the remote db + /// Deletes a measurement entry Future deleteEntry(int id, int categoryId) async { final int categoryIndex = _categories.indexWhere((category) => category.id == categoryId); if (categoryIndex == -1) throw NoSuchEntryException(); @@ -150,7 +166,26 @@ class MeasurementProvider with ChangeNotifier { if (response.statusCode >= 400) { _categories[categoryIndex].entries.insert(entryIndex, entry); notifyListeners(); - throw WgerHttpException(response.body); } } + + /// Edits a measurement entry + /// Currently there isn't any fallback if the call to the api is unsuccessful, as WgerBaseProvider.patch only returns the response body and not the whole response + Future editEntry(int id, int categroyId, num? newValue, String? newNotes) async { + final MeasurementCategory category = _categories.firstWhere( + (category) => category.id == categroyId, + orElse: () => throw NoSuchEntryException()); + final MeasurementEntry oldEntry = category.entries + .firstWhere((entry) => entry.id == id, orElse: () => throw NoSuchEntryException()); + final int entryIndex = category.entries.indexOf(oldEntry); + category.entries.removeAt(entryIndex); + final MeasurementEntry tempNewEntry = oldEntry.copyWith(value: newValue, notes: newNotes); + + final Map response = + await baseProvider.patch(tempNewEntry.toJson(), baseProvider.makeUrl(_entryUrl)); + + final MeasurementEntry newEntry = MeasurementEntry.fromJson(response); + category.entries.insert(entryIndex, newEntry); + notifyListeners(); + } } diff --git a/test/fixtures/measurement_category_edited.json b/test/fixtures/measurement_category_edited.json new file mode 100644 index 00000000..ca35e88e --- /dev/null +++ b/test/fixtures/measurement_category_edited.json @@ -0,0 +1,5 @@ +{ + "id": 1, + "name": "Triceps", + "unit": "m" +} \ No newline at end of file diff --git a/test/fixtures/measurement_category_edited_to_json.json b/test/fixtures/measurement_category_edited_to_json.json new file mode 100644 index 00000000..90bbf318 --- /dev/null +++ b/test/fixtures/measurement_category_edited_to_json.json @@ -0,0 +1,6 @@ +{ + "id": 1, + "name": "Triceps", + "unit": "m", + "entries": null +} \ No newline at end of file diff --git a/test/fixtures/measurement_category_toJson_without_id.json b/test/fixtures/measurement_category_without_id_to_json.json similarity index 100% rename from test/fixtures/measurement_category_toJson_without_id.json rename to test/fixtures/measurement_category_without_id_to_json.json diff --git a/test/fixtures/measurement_entry_edited.json b/test/fixtures/measurement_entry_edited.json new file mode 100644 index 00000000..656618f7 --- /dev/null +++ b/test/fixtures/measurement_entry_edited.json @@ -0,0 +1,7 @@ +{ + "id": 1, + "category": 1, + "date": "2021-07-21", + "value": 23, + "notes": "I just wanted to edit this to see what happens" +} \ No newline at end of file diff --git a/test/measurements/measurement_entry_test.dart b/test/measurements/measurement_entry_test.dart index 6d22c0b1..05fefa31 100644 --- a/test/measurements/measurement_entry_test.dart +++ b/test/measurements/measurement_entry_test.dart @@ -33,4 +33,23 @@ void main() { // assert expect(result, tMeasurementEntryMap); }); + + test('should copyWith objects of this class', () { + // arrange + + MeasurementEntry tMeasurementEntryCopied = MeasurementEntry( + id: 83, + category: 17, + date: DateTime(1960), + value: 93, + notes: 'Interesting', + ); + + // act + final result = tMeasurementEntry.copyWith( + id: 83, category: 17, date: DateTime(1960), value: 93, notes: 'Interesting'); + + // assert + expect(result, tMeasurementEntryCopied); + }); } diff --git a/test/measurements/measurement_provider_test.dart b/test/measurements/measurement_provider_test.dart index 2979a934..c02b629c 100644 --- a/test/measurements/measurement_provider_test.dart +++ b/test/measurements/measurement_provider_test.dart @@ -176,7 +176,7 @@ main() { MeasurementCategory(id: null, name: 'Strength', unit: 'kN'); Map tMeasurementCategoryMap = jsonDecode(fixture('measurement_category.json')); Map tMeasurementCategoryMapWithoutId = - jsonDecode(fixture('measurement_category_toJson_without_id.json')); + jsonDecode(fixture('measurement_category_without_id_to_json.json')); List tMeasurementCategoriesAdded = [ MeasurementCategory(id: 2, name: 'Biceps', unit: 'cm'), MeasurementCategory(id: 1, name: 'Strength', unit: 'kN'), @@ -253,6 +253,49 @@ main() { }); }); + group('editCategory()', () { + String tCategoryEditedName = 'Triceps'; + String tCategoryEditedUnit = 'm'; + Map tCategoryMapEditedToJson = + jsonDecode(fixture('measurement_category_edited_to_json.json')); + Map tCategoryMapEdited = + jsonDecode(fixture('measurement_category_edited.json')); + setUp(() async { + when(mockWgerBaseProvider.patch(any, any)) + .thenAnswer((realInvocation) => Future.value(tCategoryMapEdited)); + await measurementProvider.fetchAndSetCategories(); + }); + test('should add the new MeasurementCategory and remove the old one', () async { + // arrange + List tMeasurementCategoriesEdited = [ + MeasurementCategory(id: 1, name: 'Triceps', unit: 'm'), + MeasurementCategory(id: 2, name: 'Biceps', unit: 'cm'), + ]; + + // act + await measurementProvider.editCategory(tCategoryId, tCategoryEditedName, tCategoryEditedUnit); + + // assert + expect(measurementProvider.categories, tMeasurementCategoriesEdited); + }); + + test('should throw a NoSuchEntryException if category doesn\'t exist', () { + // act & assert + expect( + () async => + await measurementProvider.editCategory(83, tCategoryEditedName, tCategoryEditedUnit), + throwsA(isA())); + }); + + test('should call api to patch the category', () async { + // act + await measurementProvider.editCategory(tCategoryId, tCategoryEditedName, tCategoryEditedUnit); + + // assert + verify(mockWgerBaseProvider.patch(tCategoryMapEditedToJson, tCategoryUri)); + }); + }); + group('addEntry()', () { MeasurementEntry tMeasurementEntry = MeasurementEntry( id: 3, @@ -440,4 +483,77 @@ main() { expect(measurementProvider.categories, tMeasurementCategories); }); }); + + group('editEntry()', () { + // remove the old MeasurementCategory + // should call api to patch the category + // should add the new MeasurementCategory from the api call + // notifyListeners() + // should re-add the old MeasurementCategory and remove the new one if call to api fails + // notifyListeners() + int tEntryId = 1; + num tEntryEditedValue = 23; + String tEntryEditedNote = 'I just wanted to edit this to see what happens'; + Map tEntryMapEdited = jsonDecode(fixture('measurement_entry_edited.json')); + setUp(() async { + when(mockWgerBaseProvider.patch(any, any)) + .thenAnswer((realInvocation) => Future.value(tEntryMapEdited)); + await measurementProvider.fetchAndSetCategories(); + await measurementProvider.fetchAndSetCategoryEntries(1); + }); + test('should add the new MeasurementCategory and remove the old one', () async { + // arrange + List tMeasurementCategoriesEdited = [ + MeasurementCategory(id: 1, name: 'Strength', unit: 'kN', entries: [ + MeasurementEntry( + id: 1, + category: 1, + date: DateTime(2021, 7, 21), + value: 23, + notes: 'I just wanted to edit this to see what happens', + ), + MeasurementEntry( + id: 2, + category: 1, + date: DateTime(2021, 7, 10), + value: 15.00, + notes: '', + ) + ]), + MeasurementCategory(id: 2, name: 'Biceps', unit: 'cm') + ]; + + // act + await measurementProvider.editEntry( + tEntryId, tCategoryId, tEntryEditedValue, tEntryEditedNote); + + // assert + expect(measurementProvider.categories, tMeasurementCategoriesEdited); + }); + + test('should throw a NoSuchEntryException if category doesn\'t exist', () { + // act & assert + expect( + () async => await measurementProvider.editEntry( + tEntryId, 83, tEntryEditedValue, tEntryEditedNote), + throwsA(isA())); + }); + + test('should throw a NoSuchEntryException if entry doesn\'t exist', () { + // act & assert + expect( + () async => await measurementProvider.editEntry( + 83, tCategoryId, tEntryEditedValue, tEntryEditedNote), + throwsA(isA())); + }); + + test('should call api to patch the category', () async { + // act + await measurementProvider.editEntry( + tEntryId, tCategoryId, tEntryEditedValue, tEntryEditedNote); + + // assert + verify(mockWgerBaseProvider.patch(tEntryMapEdited, tCategoryEntriesUri)); + }); + }); }