diff --git a/lib/openfoodfacts.dart b/lib/openfoodfacts.dart index cff12a2951..333e47955f 100644 --- a/lib/openfoodfacts.dart +++ b/lib/openfoodfacts.dart @@ -26,6 +26,11 @@ export 'src/model/nutrient.dart'; export 'src/model/nutrient_levels.dart'; export 'src/model/nutrient_modifier.dart'; export 'src/model/nutriments.dart'; +export 'src/model/nutriscore/nutriscore_data_2021.dart'; +export 'src/model/nutriscore/nutriscore_data_2023.dart'; +export 'src/model/nutriscore/nutriscore_detail_2021.dart'; +export 'src/model/nutriscore/nutriscore_detail_2023.dart'; +export 'src/model/nutriscore/nutriscore_details.dart'; // export 'src/model/product_list.dart'; // not needed export 'src/model/ocr_ingredients_result.dart'; diff --git a/lib/src/model/nutriscore/nutriscore_data_2021.dart b/lib/src/model/nutriscore/nutriscore_data_2021.dart new file mode 100644 index 0000000000..4095ca06f9 --- /dev/null +++ b/lib/src/model/nutriscore/nutriscore_data_2021.dart @@ -0,0 +1,51 @@ +import 'package:json_annotation/json_annotation.dart'; +import '../../interface/json_object.dart'; +import '../../utils/json_helper.dart'; + +part 'nutriscore_data_2021.g.dart'; + +/// Detailed data of Nutri-Score version 2021. +@JsonSerializable() +class NutriScoreData2021 extends JsonObject { + @JsonKey( + name: 'is_beverage', + toJson: JsonHelper.boolToJSON, + fromJson: JsonObject.parseBool, + ) + bool? isBeverage; + + @JsonKey( + name: 'is_cheese', + toJson: JsonHelper.boolToJSON, + fromJson: JsonObject.parseBool, + ) + bool? isCheese; + + @JsonKey( + name: 'is_fat', + toJson: JsonHelper.boolToJSON, + fromJson: JsonObject.parseBool, + ) + bool? isFat; + + @JsonKey( + name: 'is_water', + toJson: JsonHelper.boolToJSON, + fromJson: JsonObject.parseBool, + ) + bool? isWater; + + @JsonKey(name: 'negative_points') + int? negativePoints; + + @JsonKey(name: 'positive_points') + int? positivePoints; + + NutriScoreData2021(); + + factory NutriScoreData2021.fromJson(Map json) => + _$NutriScoreData2021FromJson(json); + + @override + Map toJson() => _$NutriScoreData2021ToJson(this); +} diff --git a/lib/src/model/nutriscore/nutriscore_data_2021.g.dart b/lib/src/model/nutriscore/nutriscore_data_2021.g.dart new file mode 100644 index 0000000000..b03fad16c5 --- /dev/null +++ b/lib/src/model/nutriscore/nutriscore_data_2021.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'nutriscore_data_2021.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +NutriScoreData2021 _$NutriScoreData2021FromJson(Map json) => + NutriScoreData2021() + ..isBeverage = JsonObject.parseBool(json['is_beverage']) + ..isCheese = JsonObject.parseBool(json['is_cheese']) + ..isFat = JsonObject.parseBool(json['is_fat']) + ..isWater = JsonObject.parseBool(json['is_water']) + ..negativePoints = (json['negative_points'] as num?)?.toInt() + ..positivePoints = (json['positive_points'] as num?)?.toInt(); + +Map _$NutriScoreData2021ToJson(NutriScoreData2021 instance) => + { + 'is_beverage': JsonHelper.boolToJSON(instance.isBeverage), + 'is_cheese': JsonHelper.boolToJSON(instance.isCheese), + 'is_fat': JsonHelper.boolToJSON(instance.isFat), + 'is_water': JsonHelper.boolToJSON(instance.isWater), + 'negative_points': instance.negativePoints, + 'positive_points': instance.positivePoints, + }; diff --git a/lib/src/model/nutriscore/nutriscore_data_2023.dart b/lib/src/model/nutriscore/nutriscore_data_2023.dart new file mode 100644 index 0000000000..3f1f9e0fa8 --- /dev/null +++ b/lib/src/model/nutriscore/nutriscore_data_2023.dart @@ -0,0 +1,74 @@ +import 'package:json_annotation/json_annotation.dart'; +import '../../interface/json_object.dart'; +import '../../utils/json_helper.dart'; + +part 'nutriscore_data_2023.g.dart'; + +/// Detailed data of Nutri-Score version 2023. +@JsonSerializable() +class NutriScoreData2023 extends JsonObject { + @JsonKey( + name: 'count_proteins', + toJson: JsonHelper.boolToJSON, + fromJson: JsonHelper.boolFromJSON, + ) + bool? countProteins; + + @JsonKey(name: 'count_proteins_reason') + String? countProteinsReason; + + @JsonKey( + name: 'is_beverage', + toJson: JsonHelper.boolToJSON, + fromJson: JsonObject.parseBool, + ) + bool? isBeverage; + + @JsonKey( + name: 'is_cheese', + toJson: JsonHelper.boolToJSON, + fromJson: JsonObject.parseBool, + ) + bool? isCheese; + + @JsonKey( + name: 'is_fat_oil_nuts_seeds', + toJson: JsonHelper.boolToJSON, + fromJson: JsonObject.parseBool, + ) + bool? isFatOilNutsSeeds; + + @JsonKey( + name: 'is_red_meat_product', + toJson: JsonHelper.boolToJSON, + fromJson: JsonObject.parseBool, + ) + bool? isRedMeatProduct; + + @JsonKey( + name: 'is_water', + toJson: JsonHelper.boolToJSON, + fromJson: JsonObject.parseBool, + ) + bool? isWater; + + @JsonKey(name: 'negative_points') + int? negativePoints; + + @JsonKey(name: 'negative_points_max') + int? negativePointsMax; + + @JsonKey(name: 'positive_points') + int? positivePoints; + + @JsonKey(name: 'positive_points_max') + int? positivePointsMax; + + NutriScoreData2023(); + + factory NutriScoreData2023.fromJson(Map json) => + _$NutriScoreData2023FromJson(json); + + @override + Map toJson() => _$NutriScoreData2023ToJson(this); +} diff --git a/lib/src/model/nutriscore/nutriscore_data_2023.g.dart b/lib/src/model/nutriscore/nutriscore_data_2023.g.dart new file mode 100644 index 0000000000..553a8d91a2 --- /dev/null +++ b/lib/src/model/nutriscore/nutriscore_data_2023.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'nutriscore_data_2023.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +NutriScoreData2023 _$NutriScoreData2023FromJson(Map json) => + NutriScoreData2023() + ..countProteins = JsonHelper.boolFromJSON(json['count_proteins']) + ..countProteinsReason = json['count_proteins_reason'] as String? + ..isBeverage = JsonObject.parseBool(json['is_beverage']) + ..isCheese = JsonObject.parseBool(json['is_cheese']) + ..isFatOilNutsSeeds = JsonObject.parseBool(json['is_fat_oil_nuts_seeds']) + ..isRedMeatProduct = JsonObject.parseBool(json['is_red_meat_product']) + ..isWater = JsonObject.parseBool(json['is_water']) + ..negativePoints = (json['negative_points'] as num?)?.toInt() + ..negativePointsMax = (json['negative_points_max'] as num?)?.toInt() + ..positivePoints = (json['positive_points'] as num?)?.toInt() + ..positivePointsMax = (json['positive_points_max'] as num?)?.toInt(); + +Map _$NutriScoreData2023ToJson(NutriScoreData2023 instance) => + { + 'count_proteins': JsonHelper.boolToJSON(instance.countProteins), + 'count_proteins_reason': instance.countProteinsReason, + 'is_beverage': JsonHelper.boolToJSON(instance.isBeverage), + 'is_cheese': JsonHelper.boolToJSON(instance.isCheese), + 'is_fat_oil_nuts_seeds': + JsonHelper.boolToJSON(instance.isFatOilNutsSeeds), + 'is_red_meat_product': JsonHelper.boolToJSON(instance.isRedMeatProduct), + 'is_water': JsonHelper.boolToJSON(instance.isWater), + 'negative_points': instance.negativePoints, + 'negative_points_max': instance.negativePointsMax, + 'positive_points': instance.positivePoints, + 'positive_points_max': instance.positivePointsMax, + }; diff --git a/lib/src/model/nutriscore/nutriscore_detail_2021.dart b/lib/src/model/nutriscore/nutriscore_detail_2021.dart new file mode 100644 index 0000000000..b5f140af60 --- /dev/null +++ b/lib/src/model/nutriscore/nutriscore_detail_2021.dart @@ -0,0 +1,58 @@ +import 'package:json_annotation/json_annotation.dart'; +import '../../interface/json_object.dart'; +import '../../utils/json_helper.dart'; +import 'nutriscore_data_2021.dart'; + +part 'nutriscore_detail_2021.g.dart'; + +/// Data of Nutri-Score version 2021. +@JsonSerializable(explicitToJson: true) +class NutriScoreDetail2021 extends JsonObject { + @JsonKey() + String? grade; + + @JsonKey() + int? score; + + @JsonKey() + NutriScoreData2021? data; + + @JsonKey(name: 'not_applicable_category') + String? notApplicableCategory; + + @JsonKey( + name: 'category_available', + toJson: JsonHelper.boolToJSON, + fromJson: JsonHelper.boolFromJSON, + ) + bool? categoryAvailable; + + @JsonKey( + name: 'nutrients_available', + toJson: JsonHelper.boolToJSON, + fromJson: JsonHelper.boolFromJSON, + ) + bool? nutrientsAvailable; + + @JsonKey( + name: 'nutriscore_applicable', + toJson: JsonHelper.boolToJSON, + fromJson: JsonHelper.boolFromJSON, + ) + bool? nutriScoreApplicable; + + @JsonKey( + name: 'nutriscore_computed', + toJson: JsonHelper.boolToJSON, + fromJson: JsonHelper.boolFromJSON, + ) + bool? nutriScoreComputed; + + NutriScoreDetail2021(); + + factory NutriScoreDetail2021.fromJson(Map json) => + _$NutriScoreDetail2021FromJson(json); + + @override + Map toJson() => _$NutriScoreDetail2021ToJson(this); +} diff --git a/lib/src/model/nutriscore/nutriscore_detail_2021.g.dart b/lib/src/model/nutriscore/nutriscore_detail_2021.g.dart new file mode 100644 index 0000000000..17ddbbe96b --- /dev/null +++ b/lib/src/model/nutriscore/nutriscore_detail_2021.g.dart @@ -0,0 +1,38 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'nutriscore_detail_2021.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +NutriScoreDetail2021 _$NutriScoreDetail2021FromJson( + Map json) => + NutriScoreDetail2021() + ..grade = json['grade'] as String? + ..score = (json['score'] as num?)?.toInt() + ..data = json['data'] == null + ? null + : NutriScoreData2021.fromJson(json['data'] as Map) + ..notApplicableCategory = json['not_applicable_category'] as String? + ..categoryAvailable = JsonHelper.boolFromJSON(json['category_available']) + ..nutrientsAvailable = + JsonHelper.boolFromJSON(json['nutrients_available']) + ..nutriScoreApplicable = + JsonHelper.boolFromJSON(json['nutriscore_applicable']) + ..nutriScoreComputed = + JsonHelper.boolFromJSON(json['nutriscore_computed']); + +Map _$NutriScoreDetail2021ToJson( + NutriScoreDetail2021 instance) => + { + 'grade': instance.grade, + 'score': instance.score, + 'data': instance.data?.toJson(), + 'not_applicable_category': instance.notApplicableCategory, + 'category_available': JsonHelper.boolToJSON(instance.categoryAvailable), + 'nutrients_available': JsonHelper.boolToJSON(instance.nutrientsAvailable), + 'nutriscore_applicable': + JsonHelper.boolToJSON(instance.nutriScoreApplicable), + 'nutriscore_computed': JsonHelper.boolToJSON(instance.nutriScoreComputed), + }; diff --git a/lib/src/model/nutriscore/nutriscore_detail_2023.dart b/lib/src/model/nutriscore/nutriscore_detail_2023.dart new file mode 100644 index 0000000000..9539ced608 --- /dev/null +++ b/lib/src/model/nutriscore/nutriscore_detail_2023.dart @@ -0,0 +1,58 @@ +import 'package:json_annotation/json_annotation.dart'; +import '../../interface/json_object.dart'; +import '../../utils/json_helper.dart'; +import 'nutriscore_data_2023.dart'; + +part 'nutriscore_detail_2023.g.dart'; + +/// Data of Nutri-Score version 2023. +@JsonSerializable(explicitToJson: true) +class NutriScoreDetail2023 extends JsonObject { + @JsonKey() + String? grade; + + @JsonKey() + int? score; + + @JsonKey() + NutriScoreData2023? data; + + @JsonKey(name: 'not_applicable_category') + String? notApplicableCategory; + + @JsonKey( + name: 'category_available', + toJson: JsonHelper.boolToJSON, + fromJson: JsonHelper.boolFromJSON, + ) + bool? categoryAvailable; + + @JsonKey( + name: 'nutrients_available', + toJson: JsonHelper.boolToJSON, + fromJson: JsonHelper.boolFromJSON, + ) + bool? nutrientsAvailable; + + @JsonKey( + name: 'nutriscore_applicable', + toJson: JsonHelper.boolToJSON, + fromJson: JsonHelper.boolFromJSON, + ) + bool? nutriScoreApplicable; + + @JsonKey( + name: 'nutriscore_computed', + toJson: JsonHelper.boolToJSON, + fromJson: JsonHelper.boolFromJSON, + ) + bool? nutriScoreComputed; + + NutriScoreDetail2023(); + + factory NutriScoreDetail2023.fromJson(Map json) => + _$NutriScoreDetail2023FromJson(json); + + @override + Map toJson() => _$NutriScoreDetail2023ToJson(this); +} diff --git a/lib/src/model/nutriscore/nutriscore_detail_2023.g.dart b/lib/src/model/nutriscore/nutriscore_detail_2023.g.dart new file mode 100644 index 0000000000..a9af395db2 --- /dev/null +++ b/lib/src/model/nutriscore/nutriscore_detail_2023.g.dart @@ -0,0 +1,38 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'nutriscore_detail_2023.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +NutriScoreDetail2023 _$NutriScoreDetail2023FromJson( + Map json) => + NutriScoreDetail2023() + ..grade = json['grade'] as String? + ..score = (json['score'] as num?)?.toInt() + ..data = json['data'] == null + ? null + : NutriScoreData2023.fromJson(json['data'] as Map) + ..notApplicableCategory = json['not_applicable_category'] as String? + ..categoryAvailable = JsonHelper.boolFromJSON(json['category_available']) + ..nutrientsAvailable = + JsonHelper.boolFromJSON(json['nutrients_available']) + ..nutriScoreApplicable = + JsonHelper.boolFromJSON(json['nutriscore_applicable']) + ..nutriScoreComputed = + JsonHelper.boolFromJSON(json['nutriscore_computed']); + +Map _$NutriScoreDetail2023ToJson( + NutriScoreDetail2023 instance) => + { + 'grade': instance.grade, + 'score': instance.score, + 'data': instance.data?.toJson(), + 'not_applicable_category': instance.notApplicableCategory, + 'category_available': JsonHelper.boolToJSON(instance.categoryAvailable), + 'nutrients_available': JsonHelper.boolToJSON(instance.nutrientsAvailable), + 'nutriscore_applicable': + JsonHelper.boolToJSON(instance.nutriScoreApplicable), + 'nutriscore_computed': JsonHelper.boolToJSON(instance.nutriScoreComputed), + }; diff --git a/lib/src/model/nutriscore/nutriscore_details.dart b/lib/src/model/nutriscore/nutriscore_details.dart new file mode 100644 index 0000000000..dd51d7cc65 --- /dev/null +++ b/lib/src/model/nutriscore/nutriscore_details.dart @@ -0,0 +1,26 @@ +import 'package:json_annotation/json_annotation.dart'; +import '../../interface/json_object.dart'; +import 'nutriscore_detail_2021.dart'; +import 'nutriscore_detail_2023.dart'; + +part 'nutriscore_details.g.dart'; + +/// Container for Nutri-Score data from both 2021 and 2023 versions, as received from the API. +@JsonSerializable(explicitToJson: true) +class NutriScoreDetails extends JsonObject { + @JsonKey(name: '2021') + NutriScoreDetail2021? v2021; + + @JsonKey(name: '2023') + NutriScoreDetail2023? v2023; + + NutriScoreDetails(); + + factory NutriScoreDetails.fromJson(Map json) => + _$NutriScoreDetailsFromJson(json); + + @override + Map toJson() => _$NutriScoreDetailsToJson(this); + + static Map? toJsonMap(NutriScoreDetails? v) => v?.toJson(); +} diff --git a/lib/src/model/nutriscore/nutriscore_details.g.dart b/lib/src/model/nutriscore/nutriscore_details.g.dart new file mode 100644 index 0000000000..d21e431b65 --- /dev/null +++ b/lib/src/model/nutriscore/nutriscore_details.g.dart @@ -0,0 +1,22 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'nutriscore_details.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +NutriScoreDetails _$NutriScoreDetailsFromJson(Map json) => + NutriScoreDetails() + ..v2021 = json['2021'] == null + ? null + : NutriScoreDetail2021.fromJson(json['2021'] as Map) + ..v2023 = json['2023'] == null + ? null + : NutriScoreDetail2023.fromJson(json['2023'] as Map); + +Map _$NutriScoreDetailsToJson(NutriScoreDetails instance) => + { + '2021': instance.v2021?.toJson(), + '2023': instance.v2023?.toJson(), + }; diff --git a/lib/src/model/product.dart b/lib/src/model/product.dart index 82dc65645d..cdb5995628 100644 --- a/lib/src/model/product.dart +++ b/lib/src/model/product.dart @@ -4,6 +4,7 @@ import '../interface/json_object.dart'; import '../utils/json_helper.dart'; import '../utils/language_helper.dart'; import '../utils/product_fields.dart'; +import 'nutriscore/nutriscore_details.dart'; import 'additives.dart'; import 'allergens.dart'; import 'attribute.dart'; @@ -416,6 +417,12 @@ class Product extends JsonObject { @JsonKey(name: 'nutrition_grade_fr') String? nutriscore; + @JsonKey( + name: 'nutriscore', + toJson: NutriScoreDetails.toJsonMap, + ) + NutriScoreDetails? nutriScoreDetails; + @JsonKey(name: 'compared_to_category') String? comparedToCategory; @JsonKey(name: 'categories') diff --git a/lib/src/model/product.g.dart b/lib/src/model/product.g.dart index c5408a2ba0..aa42805515 100644 --- a/lib/src/model/product.g.dart +++ b/lib/src/model/product.g.dart @@ -132,6 +132,10 @@ Product _$ProductFromJson(Map json) => Product( ..allergensTagsInLanguages = LanguageHelper.fromJsonStringsListMap( json['allergens_tags_in_languages']) ..nutritionData = JsonHelper.checkboxFromJSON(json['nutrition_data']) + ..nutriScoreDetails = json['nutriscore'] == null + ? null + : NutriScoreDetails.fromJson( + json['nutriscore'] as Map) ..comparedToCategory = json['compared_to_category'] as String? ..packagings = (json['packagings'] as List?) ?.map(ProductPackaging.fromJson) @@ -299,6 +303,9 @@ Map _$ProductToJson(Product instance) => { if (instance.nutrimentDataPer case final value?) 'nutrition_data_per': value, if (instance.nutriscore case final value?) 'nutrition_grade_fr': value, + if (NutriScoreDetails.toJsonMap(instance.nutriScoreDetails) + case final value?) + 'nutriscore': value, if (instance.comparedToCategory case final value?) 'compared_to_category': value, if (instance.categories case final value?) 'categories': value, diff --git a/lib/src/utils/product_fields.dart b/lib/src/utils/product_fields.dart index 20e7e35937..ef1aaa1d7a 100644 --- a/lib/src/utils/product_fields.dart +++ b/lib/src/utils/product_fields.dart @@ -128,6 +128,7 @@ enum ProductField implements OffTagged { NUTRIMENT_DATA_PER(offTag: 'nutrition_data_per'), NUTRITION_DATA(offTag: 'nutrition_data'), NUTRISCORE(offTag: 'nutrition_grade_fr'), + NUTRISCORE_DETAILS(offTag: 'nutriscore'), COMPARED_TO_CATEGORY(offTag: 'compared_to_category'), CATEGORIES(offTag: 'categories'), CATEGORIES_TAGS( diff --git a/test/api_get_product_test.dart b/test/api_get_product_test.dart index a115f91133..5428b9c8a4 100644 --- a/test/api_get_product_test.dart +++ b/test/api_get_product_test.dart @@ -187,6 +187,169 @@ void main() { ); }); + test('check nutriscore data for alcohol', () async { + const String barcode = '3119780259625'; + const String category = 'en:alcoholic-beverages'; + + final ProductQueryConfiguration configurations = + ProductQueryConfiguration( + barcode, + language: OpenFoodFactsLanguage.ENGLISH, + fields: [ProductField.NUTRISCORE_DETAILS], + version: ProductQueryVersion.v3, + ); + final ProductResultV3 result = await getProductV3InProd( + configurations, + ); + expect(result.status, ProductResultV3.statusSuccess); + expect(result.barcode, barcode); + expect(result.product, isNotNull); + + final nutriScoreDetails = result.product!.nutriScoreDetails; + expect(nutriScoreDetails, isNotNull); + + // test Nutri-Score with raw data + final nutriScore2021 = nutriScoreDetails?.v2021; + expect(nutriScore2021, isNotNull); + expect(nutriScore2021!.grade, "not-applicable"); + expect(nutriScore2021.categoryAvailable, isTrue); + expect(nutriScore2021.score, isNull); + expect(nutriScore2021.nutrientsAvailable, isTrue); + expect(nutriScore2021.nutriScoreComputed, isFalse); + expect(nutriScore2021.nutriScoreApplicable, isFalse); + expect(nutriScore2021.data, isNotNull); + expect(nutriScore2021.data!.isBeverage, isTrue); + expect(nutriScore2021.data!.isWater, isFalse); + expect(nutriScore2021.data!.isCheese, isFalse); + expect(nutriScore2021.data!.isFat, isFalse); + expect(nutriScore2021.notApplicableCategory, category); + + final nutriScore2023 = nutriScoreDetails?.v2023; + expect(nutriScore2023, isNotNull); + expect(nutriScore2023!.grade, "not-applicable"); + expect(nutriScore2023.score, isNull); + expect(nutriScore2023.data, isNotNull); + expect(nutriScore2023.notApplicableCategory, category); + expect(nutriScore2023.data!.isBeverage, isTrue); + expect(nutriScore2023.data!.isRedMeatProduct, isFalse); + expect(nutriScore2023.data!.isFatOilNutsSeeds, isFalse); + expect(nutriScore2023.data!.isWater, isFalse); + expect(nutriScore2023.data!.isCheese, isFalse); + + // test serialization + final productJson = result.product!.toJson(); + final productRestored = Product.fromJson(productJson); + expect(productRestored.nutriScoreDetails, isNotNull); + + final restored2021 = productRestored.nutriScoreDetails!.v2021; + expect(restored2021, isNotNull); + expect(restored2021!.grade, "not-applicable"); + expect(restored2021.categoryAvailable, isTrue); + expect(restored2021.score, isNull); + expect(restored2021.nutrientsAvailable, isTrue); + expect(restored2021.nutriScoreComputed, isFalse); + expect(restored2021.nutriScoreApplicable, isFalse); + expect(restored2021.data, isNotNull); + expect(restored2021.data!.isBeverage, isTrue); + expect(restored2021.data!.isWater, isFalse); + expect(restored2021.data!.isCheese, isFalse); + expect(restored2021.data!.isFat, isFalse); + expect(restored2021.notApplicableCategory, category); + + final restored2023 = productRestored.nutriScoreDetails!.v2023; + expect(restored2023, isNotNull); + expect(restored2023!.grade, "not-applicable"); + expect(restored2023.score, isNull); + expect(restored2023.data, isNotNull); + expect(restored2023.notApplicableCategory, category); + expect(restored2023.data!.isBeverage, isTrue); + expect(restored2023.data!.isRedMeatProduct, isFalse); + expect(restored2023.data!.isFatOilNutsSeeds, isFalse); + expect(restored2023.data!.isWater, isFalse); + expect(restored2023.data!.isCheese, isFalse); + }); + + test('check nutriscore data for cookies', () async { + const String barcode = BARCODE_DANISH_BUTTER_COOKIES; + final ProductQueryConfiguration configurations = + ProductQueryConfiguration( + barcode, + language: OpenFoodFactsLanguage.GERMAN, + fields: [ProductField.NUTRISCORE_DETAILS], + version: ProductQueryVersion.v3, + ); + final ProductResultV3 result = await getProductV3InProd( + configurations, + ); + + expect(result.status, ProductResultV3.statusSuccess); + expect(result.barcode, barcode); + expect(result.product, isNotNull); + + final nutriScoreDetails = result.product!.nutriScoreDetails; + expect(nutriScoreDetails, isNotNull); + + // test Nutri-Score with raw data + final nutriScore2021 = nutriScoreDetails?.v2021; + expect(nutriScore2021, isNotNull); + expect(nutriScore2021!.grade, "e"); + expect(nutriScore2021.categoryAvailable, isTrue); + expect(nutriScore2021.score, 23); + expect(nutriScore2021.nutrientsAvailable, isTrue); + expect(nutriScore2021.nutriScoreComputed, isTrue); + expect(nutriScore2021.nutriScoreApplicable, isTrue); + expect(nutriScore2021.data, isNotNull); + expect(nutriScore2021.data!.isBeverage, isFalse); + expect(nutriScore2021.data!.isWater, isFalse); + expect(nutriScore2021.data!.isCheese, isFalse); + expect(nutriScore2021.data!.isFat, isFalse); + expect(nutriScore2021.notApplicableCategory, isNull); + + final nutriScore2023 = nutriScoreDetails?.v2023; + expect(nutriScore2023, isNotNull); + expect(nutriScore2023!.grade, "e"); + expect(nutriScore2023.score, 25); + expect(nutriScore2023.data, isNotNull); + expect(nutriScore2023.notApplicableCategory, isNull); + expect(nutriScore2023.data!.isBeverage, isFalse); + expect(nutriScore2023.data!.isRedMeatProduct, isFalse); + expect(nutriScore2023.data!.isFatOilNutsSeeds, isFalse); + expect(nutriScore2023.data!.isWater, isFalse); + expect(nutriScore2023.data!.isCheese, isFalse); + + // test serialization + final productJson = result.product!.toJson(); + final productRestored = Product.fromJson(productJson); + expect(productRestored.nutriScoreDetails, isNotNull); + + final restored2021 = productRestored.nutriScoreDetails!.v2021; + expect(restored2021, isNotNull); + expect(restored2021!.grade, "e"); + expect(restored2021.categoryAvailable, isTrue); + expect(restored2021.score, 23); + expect(restored2021.nutrientsAvailable, isTrue); + expect(restored2021.nutriScoreComputed, isTrue); + expect(restored2021.nutriScoreApplicable, isTrue); + expect(restored2021.data, isNotNull); + expect(restored2021.data!.isBeverage, isFalse); + expect(restored2021.data!.isWater, isFalse); + expect(restored2021.data!.isCheese, isFalse); + expect(restored2021.data!.isFat, isFalse); + expect(restored2021.notApplicableCategory, isNull); + + final restored2023 = productRestored.nutriScoreDetails!.v2023; + expect(restored2023, isNotNull); + expect(restored2023!.grade, "e"); + expect(restored2023.score, 25); + expect(restored2023.data, isNotNull); + expect(restored2023.notApplicableCategory, isNull); + expect(restored2023.data!.isBeverage, isFalse); + expect(restored2023.data!.isRedMeatProduct, isFalse); + expect(restored2023.data!.isFatOilNutsSeeds, isFalse); + expect(restored2023.data!.isWater, isFalse); + expect(restored2023.data!.isCheese, isFalse); + }); + test('get product Danish Butter Cookies & Chocolate Chip Cookies', () async { const String barcode = BARCODE_DANISH_BUTTER_COOKIES;