diff --git a/lib/src/model/product.dart b/lib/src/model/product.dart index 14d59de883..4374df2b8d 100644 --- a/lib/src/model/product.dart +++ b/lib/src/model/product.dart @@ -106,6 +106,12 @@ class Product extends JsonObject { String? brands; @JsonKey(name: 'brands_tags', includeIfNull: false) List? brandsTags; + @JsonKey( + name: 'brands_tags_in_languages', + toJson: LanguageHelper.toJsonStringsListMap, + fromJson: LanguageHelper.fromJsonStringsListMap, + includeIfNull: false) + Map>? brandsTagsInLanguages; @JsonKey(name: 'countries', includeIfNull: false) String? countries; @@ -218,6 +224,12 @@ class Product extends JsonObject { fromJson: IngredientsAnalysisTags.fromJson, toJson: IngredientsAnalysisTags.toJson) IngredientsAnalysisTags? ingredientsAnalysisTags; + @JsonKey( + name: 'ingredients_analysis_tags_in_languages', + toJson: LanguageHelper.toJsonStringsListMap, + fromJson: LanguageHelper.fromJsonStringsListMap, + includeIfNull: false) + Map>? ingredientsAnalysisTagsInLanguages; /// When no nutrition data is true, nutriments are always null. /// @@ -327,15 +339,41 @@ class Product extends JsonObject { includeIfNull: false) Map? packagingTextInLanguages; - @JsonKey(name: 'misc', includeIfNull: false) + @JsonKey(name: 'misc_tags', includeIfNull: false) List? miscTags; + @JsonKey( + name: 'misc_tags_in_languages', + toJson: LanguageHelper.toJsonStringsListMap, + fromJson: LanguageHelper.fromJsonStringsListMap, + includeIfNull: false) + Map>? miscTagsInLanguages; + @JsonKey(name: 'states_tags', includeIfNull: false) List? statesTags; + @JsonKey( + name: 'states_tags_in_languages', + toJson: LanguageHelper.toJsonStringsListMap, + fromJson: LanguageHelper.fromJsonStringsListMap, + includeIfNull: false) + Map>? statesTagsInLanguages; + @JsonKey(name: 'traces_tags', includeIfNull: false) List? tracesTags; + @JsonKey( + name: 'traces_tags_in_languages', + toJson: LanguageHelper.toJsonStringsListMap, + fromJson: LanguageHelper.fromJsonStringsListMap, + includeIfNull: false) + Map>? tracesTagsInLanguages; + @JsonKey(name: 'stores_tags', includeIfNull: false) List? storesTags; - + @JsonKey( + name: 'stores_tags_in_languages', + toJson: LanguageHelper.toJsonStringsListMap, + fromJson: LanguageHelper.fromJsonStringsListMap, + includeIfNull: false) + Map>? storesTagsInLanguages; @JsonKey(name: 'stores', includeIfNull: false) String? stores; @@ -511,6 +549,140 @@ class Product extends JsonObject { factory Product.fromJson(Map json) { final Product result = _$ProductFromJson(json); + + void setLanguageString( + final ProductField productField, + final OpenFoodFactsLanguage language, + final String label, + ) { + switch (productField) { + case ProductField.NAME_IN_LANGUAGES: + case ProductField.NAME_ALL_LANGUAGES: + result.productNameInLanguages ??= {}; + result.productNameInLanguages![language] = label; + break; + case ProductField.INGREDIENTS_TEXT_IN_LANGUAGES: + case ProductField.INGREDIENTS_TEXT_ALL_LANGUAGES: + result.ingredientsTextInLanguages ??= {}; + result.ingredientsTextInLanguages![language] = label; + break; + case ProductField.PACKAGING_TEXT_IN_LANGUAGES: + case ProductField.PACKAGING_TEXT_ALL_LANGUAGES: + result.packagingTextInLanguages ??= {}; + result.packagingTextInLanguages![language] = label; + break; + default: + // not supposed to be called with other ProductField values. + assert(false); + } + } + + void setLanguageListString( + final ProductField productField, + final OpenFoodFactsLanguage language, + final Map json, + final String key, + ) { + final List? labels = _jsonValueToList(json[key]); + if (labels == null) { + return; + } + switch (productField) { + case ProductField.CATEGORIES_TAGS_IN_LANGUAGES: + result.categoriesTagsInLanguages ??= {}; + result.categoriesTagsInLanguages![language] = labels; + break; + case ProductField.TRACES_TAGS_IN_LANGUAGES: + result.tracesTagsInLanguages ??= {}; + result.tracesTagsInLanguages![language] = labels; + break; + case ProductField.BRANDS_TAGS_IN_LANGUAGES: + result.brandsTagsInLanguages ??= {}; + result.brandsTagsInLanguages![language] = labels; + break; + case ProductField.STATES_TAGS_IN_LANGUAGES: + result.statesTagsInLanguages ??= {}; + result.statesTagsInLanguages![language] = labels; + break; + case ProductField.STORES_TAGS_IN_LANGUAGES: + result.storesTagsInLanguages ??= {}; + result.storesTagsInLanguages![language] = labels; + break; + case ProductField.MISC_TAGS_IN_LANGUAGES: + result.miscTagsInLanguages ??= {}; + result.miscTagsInLanguages![language] = labels; + break; + case ProductField.INGREDIENTS_ANALYSIS_TAGS_IN_LANGUAGES: + result.ingredientsAnalysisTagsInLanguages ??= {}; + result.ingredientsAnalysisTagsInLanguages![language] = labels; + break; + case ProductField.INGREDIENTS_TAGS_IN_LANGUAGES: + result.ingredientsTagsInLanguages ??= {}; + result.ingredientsTagsInLanguages![language] = labels; + break; + case ProductField.LABELS_TAGS_IN_LANGUAGES: + result.labelsTagsInLanguages ??= {}; + result.labelsTagsInLanguages![language] = labels; + break; + case ProductField.COUNTRIES_TAGS_IN_LANGUAGES: + result.countriesTagsInLanguages ??= {}; + result.countriesTagsInLanguages![language] = labels; + break; + default: + // not supposed to be called with other ProductField values. + assert(false); + } + } + + void addInLanguagesData( + final ProductField productField, + final OpenFoodFactsLanguage language, + final Map json, + final String key, + ) { + switch (productField) { + case ProductField.NAME_IN_LANGUAGES: + case ProductField.INGREDIENTS_TEXT_IN_LANGUAGES: + case ProductField.PACKAGING_TEXT_IN_LANGUAGES: + setLanguageString(productField, language, json[key]); + return; + case ProductField.CATEGORIES_TAGS_IN_LANGUAGES: + case ProductField.TRACES_TAGS_IN_LANGUAGES: + case ProductField.BRANDS_TAGS_IN_LANGUAGES: + case ProductField.STATES_TAGS_IN_LANGUAGES: + case ProductField.STORES_TAGS_IN_LANGUAGES: + case ProductField.MISC_TAGS_IN_LANGUAGES: + case ProductField.INGREDIENTS_ANALYSIS_TAGS_IN_LANGUAGES: + case ProductField.INGREDIENTS_TAGS_IN_LANGUAGES: + case ProductField.LABELS_TAGS_IN_LANGUAGES: + case ProductField.COUNTRIES_TAGS_IN_LANGUAGES: + setLanguageListString(productField, language, json, key); + return; + case ProductField.IMAGES_FRESHNESS_IN_LANGUAGES: + final Map values = + _jsonValueToImagesFreshness(json[key], language); + result.imagesFreshnessInLanguages ??= {}; + result.imagesFreshnessInLanguages![language] = values; + return; + default: + if (fieldsInLanguages.contains(productField)) { + throw Exception('Unhandled in-languages case for $productField'); + } + } + } + + ProductField? extractProductField( + final String key, + final Iterable iterable, + ) { + for (final ProductField productField in iterable) { + if (key.startsWith(productField.offTag)) { + return productField; + } + } + return null; + } + for (final String key in json.keys) { if (key.contains('debug')) { continue; @@ -526,95 +698,28 @@ class Product extends JsonObject { // (`product_name_[2 letter language code]`). // We store those values in a more structured maps like // [productNameInLanguages]. - if (key == ProductField.NAME_ALL_LANGUAGES.offTag) { - final Map? localized = - _getLocalizedStrings(json[key]); - if (localized != null) { - result.productNameInLanguages ??= {}; - result.productNameInLanguages!.addAll(localized); - } - } else if (key == ProductField.INGREDIENTS_TEXT_ALL_LANGUAGES.offTag) { - final Map? localized = - _getLocalizedStrings(json[key]); - if (localized != null) { - result.ingredientsTextInLanguages ??= {}; - result.ingredientsTextInLanguages!.addAll(localized); - } - } else if (key == ProductField.PACKAGING_TEXT_ALL_LANGUAGES.offTag) { + + ProductField? productField = extractProductField(key, fieldsAllLanguages); + if (productField != null) { final Map? localized = _getLocalizedStrings(json[key]); if (localized != null) { - result.packagingTextInLanguages ??= {}; - result.packagingTextInLanguages!.addAll(localized); - } - } else if (key.startsWith(ProductField.NAME_IN_LANGUAGES.offTag)) { - final OpenFoodFactsLanguage lang = - _langFrom(key, ProductField.NAME_IN_LANGUAGES.offTag); - if (lang != OpenFoodFactsLanguage.UNDEFINED) { - result.productNameInLanguages ??= {}; - result.productNameInLanguages![lang] = json[key]; - } - } else if (key - .startsWith(ProductField.CATEGORIES_TAGS_IN_LANGUAGES.offTag)) { - final OpenFoodFactsLanguage lang = - _langFrom(key, ProductField.CATEGORIES_TAGS_IN_LANGUAGES.offTag); - final values = _jsonValueToList(json[key]); - if (lang != OpenFoodFactsLanguage.UNDEFINED && values != null) { - result.categoriesTagsInLanguages ??= {}; - result.categoriesTagsInLanguages![lang] = values; - } - } else if (key - .startsWith(ProductField.INGREDIENTS_TAGS_IN_LANGUAGES.offTag)) { - final OpenFoodFactsLanguage lang = - _langFrom(key, ProductField.INGREDIENTS_TAGS_IN_LANGUAGES.offTag); - final values = _jsonValueToList(json[key]); - if (lang != OpenFoodFactsLanguage.UNDEFINED && values != null) { - result.ingredientsTagsInLanguages ??= {}; - result.ingredientsTagsInLanguages![lang] = values; - } - } else if (key - .startsWith(ProductField.IMAGES_FRESHNESS_IN_LANGUAGES.offTag)) { - final OpenFoodFactsLanguage lang = - _langFrom(key, ProductField.IMAGES_FRESHNESS_IN_LANGUAGES.offTag); - if (lang != OpenFoodFactsLanguage.UNDEFINED) { - final Map values = - _jsonValueToImagesFreshness(json[key], lang); - result.imagesFreshnessInLanguages ??= {}; - result.imagesFreshnessInLanguages![lang] = values; - } - } else if (key.startsWith(ProductField.LABELS_TAGS_IN_LANGUAGES.offTag)) { - final OpenFoodFactsLanguage lang = - _langFrom(key, ProductField.LABELS_TAGS_IN_LANGUAGES.offTag); - final values = _jsonValueToList(json[key]); - if (lang != OpenFoodFactsLanguage.UNDEFINED && values != null) { - result.labelsTagsInLanguages ??= {}; - result.labelsTagsInLanguages![lang] = values; - } - } else if (key - .startsWith(ProductField.COUNTRIES_TAGS_IN_LANGUAGES.offTag)) { - final OpenFoodFactsLanguage lang = - _langFrom(key, ProductField.COUNTRIES_TAGS_IN_LANGUAGES.offTag); - final values = _jsonValueToList(json[key]); - if (lang != OpenFoodFactsLanguage.UNDEFINED && values != null) { - result.countriesTagsInLanguages ??= {}; - result.countriesTagsInLanguages![lang] = values; - } - } else if (key - .startsWith(ProductField.INGREDIENTS_TEXT_IN_LANGUAGES.offTag)) { - final OpenFoodFactsLanguage lang = - _langFrom(key, ProductField.INGREDIENTS_TEXT_IN_LANGUAGES.offTag); - if (lang != OpenFoodFactsLanguage.UNDEFINED) { - result.ingredientsTextInLanguages ??= {}; - result.ingredientsTextInLanguages![lang] = json[key]; + for (final MapEntry entry + in localized.entries) { + setLanguageString(productField, entry.key, entry.value); + } } - } else if (key - .startsWith(ProductField.PACKAGING_TEXT_IN_LANGUAGES.offTag)) { - final OpenFoodFactsLanguage lang = - _langFrom(key, ProductField.PACKAGING_TEXT_IN_LANGUAGES.offTag); - if (lang != OpenFoodFactsLanguage.UNDEFINED) { - result.packagingTextInLanguages ??= {}; - result.packagingTextInLanguages![lang] = json[key]; + continue; + } + + productField = extractProductField(key, fieldsInLanguages); + if (productField != null) { + final OpenFoodFactsLanguage language = + _langFrom(key, productField.offTag); + if (language != OpenFoodFactsLanguage.UNDEFINED) { + addInLanguagesData(productField, language, json, key); } + continue; } } return result; diff --git a/lib/src/model/product.g.dart b/lib/src/model/product.g.dart index 4f454d1600..63a2e3469f 100644 --- a/lib/src/model/product.g.dart +++ b/lib/src/model/product.g.dart @@ -73,8 +73,9 @@ Product _$ProductFromJson(Map json) => Product( packagingTags: (json['packaging_tags'] as List?) ?.map((e) => e as String) .toList(), - miscTags: - (json['misc'] as List?)?.map((e) => e as String).toList(), + miscTags: (json['misc_tags'] as List?) + ?.map((e) => e as String) + .toList(), statesTags: (json['states_tags'] as List?) ?.map((e) => e as String) .toList(), @@ -100,6 +101,8 @@ Product _$ProductFromJson(Map json) => Product( : Nutriments.fromJson(json['nutriments'] as Map), noNutritionData: JsonHelper.checkboxFromJSON(json['no_nutrition_data']), ) + ..brandsTagsInLanguages = LanguageHelper.fromJsonStringsListMap( + json['brands_tags_in_languages']) ..imagesFreshnessInLanguages = (json['imagesFreshnessInLanguages'] as Map?)?.map( (k, e) => MapEntry( @@ -108,6 +111,9 @@ Product _$ProductFromJson(Map json) => Product( (k, e) => MapEntry($enumDecode(_$ImageFieldEnumMap, k), e as int), )), ) + ..ingredientsAnalysisTagsInLanguages = + LanguageHelper.fromJsonStringsListMap( + json['ingredients_analysis_tags_in_languages']) ..nutritionData = JsonHelper.checkboxFromJSON(json['nutrition_data']) ..comparedToCategory = json['compared_to_category'] as String? ..packagings = (json['packagings'] as List?) @@ -117,6 +123,14 @@ Product _$ProductFromJson(Map json) => Product( JsonHelper.boolFromJSON(json['packagings_complete']) ..packagingTextInLanguages = LanguageHelper.fromJsonStringMap(json['packaging_text_in_languages']) + ..miscTagsInLanguages = + LanguageHelper.fromJsonStringsListMap(json['misc_tags_in_languages']) + ..statesTagsInLanguages = LanguageHelper.fromJsonStringsListMap( + json['states_tags_in_languages']) + ..tracesTagsInLanguages = LanguageHelper.fromJsonStringsListMap( + json['traces_tags_in_languages']) + ..storesTagsInLanguages = LanguageHelper.fromJsonStringsListMap( + json['stores_tags_in_languages']) ..lastModifiedBy = json['last_modified_by'] as String? ..lastImage = JsonHelper.timestampToDate(json['last_image_t']) ..lastEditor = json['last_editor'] as String? @@ -164,6 +178,8 @@ Map _$ProductToJson(Product instance) { writeNotNull('generic_name', instance.genericName); writeNotNull('brands', instance.brands); writeNotNull('brands_tags', instance.brandsTags); + writeNotNull('brands_tags_in_languages', + LanguageHelper.toJsonStringsListMap(instance.brandsTagsInLanguages)); writeNotNull('countries', instance.countries); writeNotNull('countries_tags', instance.countriesTags); writeNotNull('countries_tags_in_languages', @@ -198,6 +214,10 @@ Map _$ProductToJson(Product instance) { e.map((k, e) => MapEntry(_$ImageFieldEnumMap[k]!, e)))); val['ingredients_analysis_tags'] = IngredientsAnalysisTags.toJson(instance.ingredientsAnalysisTags); + writeNotNull( + 'ingredients_analysis_tags_in_languages', + LanguageHelper.toJsonStringsListMap( + instance.ingredientsAnalysisTagsInLanguages)); writeNotNull('additives_tags', Additives.additivesToJson(instance.additives)); writeNotNull('allergens_tags', Allergens.allergensToJson(instance.allergens)); writeNotNull( @@ -223,10 +243,18 @@ Map _$ProductToJson(Product instance) { writeNotNull('packaging_tags', instance.packagingTags); writeNotNull('packaging_text_in_languages', LanguageHelper.toJsonStringMap(instance.packagingTextInLanguages)); - writeNotNull('misc', instance.miscTags); + writeNotNull('misc_tags', instance.miscTags); + writeNotNull('misc_tags_in_languages', + LanguageHelper.toJsonStringsListMap(instance.miscTagsInLanguages)); writeNotNull('states_tags', instance.statesTags); + writeNotNull('states_tags_in_languages', + LanguageHelper.toJsonStringsListMap(instance.statesTagsInLanguages)); writeNotNull('traces_tags', instance.tracesTags); + writeNotNull('traces_tags_in_languages', + LanguageHelper.toJsonStringsListMap(instance.tracesTagsInLanguages)); writeNotNull('stores_tags', instance.storesTags); + writeNotNull('stores_tags_in_languages', + LanguageHelper.toJsonStringsListMap(instance.storesTagsInLanguages)); writeNotNull('stores', instance.stores); writeNotNull('attribute_groups', JsonHelper.attributeGroupsToJson(instance.attributeGroups)); diff --git a/lib/src/utils/product_fields.dart b/lib/src/utils/product_fields.dart index 1e63f3eb45..da1a2be219 100644 --- a/lib/src/utils/product_fields.dart +++ b/lib/src/utils/product_fields.dart @@ -10,6 +10,7 @@ enum ProductField implements OffTagged { GENERIC_NAME(offTag: 'generic_name'), BRANDS(offTag: 'brands'), BRANDS_TAGS(offTag: 'brands_tags'), + BRANDS_TAGS_IN_LANGUAGES(offTag: 'brands_tags_'), COUNTRIES(offTag: 'countries'), COUNTRIES_TAGS(offTag: 'countries_tags'), COUNTRIES_TAGS_IN_LANGUAGES(offTag: 'countries_tags_'), @@ -57,12 +58,17 @@ enum ProductField implements OffTagged { PACKAGING_TAGS(offTag: 'packaging_tags'), PACKAGING_TEXT_IN_LANGUAGES(offTag: 'packaging_text_'), PACKAGING_TEXT_ALL_LANGUAGES(offTag: 'packaging_text_languages'), - MISC_TAGS(offTag: 'misc'), + MISC_TAGS(offTag: 'misc_tags'), + MISC_TAGS_IN_LANGUAGES(offTag: 'misc_tags_'), STATES_TAGS(offTag: 'states_tags'), + STATES_TAGS_IN_LANGUAGES(offTag: 'states_tags_'), TRACES_TAGS(offTag: 'traces_tags'), + TRACES_TAGS_IN_LANGUAGES(offTag: 'traces_tags_'), STORES_TAGS(offTag: 'stores_tags'), + STORES_TAGS_IN_LANGUAGES(offTag: 'stores_tags_'), STORES(offTag: 'stores'), INGREDIENTS_ANALYSIS_TAGS(offTag: 'ingredients_analysis_tags'), + INGREDIENTS_ANALYSIS_TAGS_IN_LANGUAGES(offTag: 'ingredients_analysis_tags_'), ALLERGENS(offTag: 'allergens_tags'), ATTRIBUTE_GROUPS(offTag: 'attribute_groups'), LAST_MODIFIED(offTag: 'last_modified_t'), @@ -100,23 +106,35 @@ enum ProductField implements OffTagged { final String offTag; } +const Set fieldsInLanguages = { + ProductField.NAME_IN_LANGUAGES, + ProductField.INGREDIENTS_TEXT_IN_LANGUAGES, + ProductField.PACKAGING_TEXT_IN_LANGUAGES, + ProductField.CATEGORIES_TAGS_IN_LANGUAGES, + ProductField.TRACES_TAGS_IN_LANGUAGES, + ProductField.STORES_TAGS_IN_LANGUAGES, + ProductField.STATES_TAGS_IN_LANGUAGES, + ProductField.BRANDS_TAGS_IN_LANGUAGES, + ProductField.MISC_TAGS_IN_LANGUAGES, + ProductField.INGREDIENTS_ANALYSIS_TAGS_IN_LANGUAGES, + ProductField.LABELS_TAGS_IN_LANGUAGES, + ProductField.COUNTRIES_TAGS_IN_LANGUAGES, + ProductField.INGREDIENTS_TAGS_IN_LANGUAGES, + ProductField.IMAGES_FRESHNESS_IN_LANGUAGES, +}; + +const Set fieldsAllLanguages = { + ProductField.NAME_ALL_LANGUAGES, + ProductField.INGREDIENTS_TEXT_ALL_LANGUAGES, + ProductField.PACKAGING_TEXT_ALL_LANGUAGES, +}; + /// NOTE: if one of the fields is IN_LANGUAGES and [languages] is empty - /// the function will throw. List convertFieldsToStrings( List fields, List languages) { final fieldsStrings = []; - const fieldsInLanguages = [ - ProductField.CATEGORIES_TAGS_IN_LANGUAGES, - ProductField.LABELS_TAGS_IN_LANGUAGES, - ProductField.NAME_IN_LANGUAGES, - ProductField.COUNTRIES_TAGS_IN_LANGUAGES, - ProductField.INGREDIENTS_TEXT_IN_LANGUAGES, - ProductField.PACKAGING_TEXT_IN_LANGUAGES, - ProductField.INGREDIENTS_TAGS_IN_LANGUAGES, - ProductField.IMAGES_FRESHNESS_IN_LANGUAGES, - ]; - for (final field in fields) { if (fieldsInLanguages.contains(field)) { if (languages.isEmpty) { diff --git a/test/api_get_localized_product_test.dart b/test/api_get_localized_product_test.dart new file mode 100644 index 0000000000..dd01b3d298 --- /dev/null +++ b/test/api_get_localized_product_test.dart @@ -0,0 +1,472 @@ +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:test/test.dart'; + +import 'test_constants.dart'; + +void main() { + OpenFoodAPIConfiguration.userAgent = TestConstants.TEST_USER_AGENT; + OpenFoodAPIConfiguration.globalUser = TestConstants.PROD_USER; + + group('$OpenFoodAPIClient get localized product fields', () { + test('get packaging text in languages (Coca-Cola)', () async { + const String barcode = '5449000000996'; + const List languages = [ + OpenFoodFactsLanguage.ENGLISH, + OpenFoodFactsLanguage.FRENCH, + ]; + + final ProductQueryConfiguration configurations = + ProductQueryConfiguration( + barcode, + languages: languages, + fields: [ProductField.PACKAGING_TEXT_IN_LANGUAGES], + version: ProductQueryVersion.v3, + ); + final ProductResultV3 result = await OpenFoodAPIClient.getProductV3( + configurations, + ); + expect(result.status, ProductResultV3.statusSuccess); + expect(result.product, isNotNull); + expect(result.product!.packagingTextInLanguages, isNotNull); + for (final OpenFoodFactsLanguage language in languages) { + expect(result.product!.packagingTextInLanguages![language], isNotNull); + } + }); + + test('get images freshness', () async { + const BARCODE_DANISH_BUTTER_COOKIES = '5701184005007'; + const List languages = [ + OpenFoodFactsLanguage.ENGLISH, + OpenFoodFactsLanguage.RUSSIAN, + OpenFoodFactsLanguage.GERMAN, + OpenFoodFactsLanguage.FRENCH, + ]; + + final ProductResultV3 productResult = + await OpenFoodAPIClient.getProductV3( + ProductQueryConfiguration( + BARCODE_DANISH_BUTTER_COOKIES, + languages: languages, + fields: [ProductField.IMAGES_FRESHNESS_IN_LANGUAGES], + version: ProductQueryVersion.v3, + ), + ); + final Product product = productResult.product!; + const int TEN_YEARS = 10 * 365 * 24 * 3600; + for (final OpenFoodFactsLanguage language in languages) { + final Map freshnesses = + product.imagesFreshnessInLanguages![language]!; + for (final ImageField imageField in ImageField.values) { + final int? freshness = freshnesses[imageField]; + if (freshness != null) { + expect(freshness >= 0, isTrue); + expect(freshness < TEN_YEARS, isTrue); + } + } + } + }); + + test('get all "tags in languages" (List)', () async { + const String barcode = '5449000000996'; + const List languages = [ + OpenFoodFactsLanguage.ENGLISH, + OpenFoodFactsLanguage.FRENCH, + ]; + + final ProductQueryConfiguration configurations = + ProductQueryConfiguration( + barcode, + languages: languages, + fields: [ + // tags in languages + ProductField.CATEGORIES_TAGS_IN_LANGUAGES, + ProductField.TRACES_TAGS_IN_LANGUAGES, + ProductField.STORES_TAGS_IN_LANGUAGES, + ProductField.STATES_TAGS_IN_LANGUAGES, + ProductField.BRANDS_TAGS_IN_LANGUAGES, + ProductField.MISC_TAGS_IN_LANGUAGES, + ProductField.INGREDIENTS_ANALYSIS_TAGS_IN_LANGUAGES, + ProductField.LABELS_TAGS_IN_LANGUAGES, + ProductField.COUNTRIES_TAGS_IN_LANGUAGES, + ProductField.INGREDIENTS_TAGS_IN_LANGUAGES, + // tags + ProductField.CATEGORIES_TAGS, + ProductField.TRACES_TAGS, + ProductField.STORES_TAGS, + ProductField.STATES_TAGS, + ProductField.BRANDS_TAGS, + ProductField.MISC_TAGS, + ProductField.INGREDIENTS_ANALYSIS_TAGS, + ProductField.LABELS_TAGS, + ProductField.COUNTRIES_TAGS, + ProductField.INGREDIENTS_TAGS, + ], + version: ProductQueryVersion.v3, + ); + final ProductResultV3 result = await OpenFoodAPIClient.getProductV3( + configurations, + ); + expect(result.status, ProductResultV3.statusSuccess); + expect(result.product, isNotNull); + final Product product = result.product!; + + void check( + final List? tags, + final Map>? tagsInLanguages, + final ProductField productField, + ) { + expect( + tags, + isNotNull, + reason: productField.offTag, + ); + expect( + tagsInLanguages, + isNotNull, + reason: productField.offTag, + ); + expect( + tagsInLanguages, + isNotEmpty, + reason: productField.offTag, + ); + final int count = tags!.length; + + for (final OpenFoodFactsLanguage language in languages) { + expect( + tagsInLanguages![language], + isNotNull, + reason: productField.offTag, + ); + expect( + tagsInLanguages[language]!.length, + count, + reason: productField.offTag, + ); + } + } + + void checkIngredientAnaysisTags( + final IngredientsAnalysisTags? tags, + final Map>? tagsInLanguages, + final ProductField productField, + ) { + expect( + tags, + isNotNull, + reason: productField.offTag, + ); + expect( + tagsInLanguages, + isNotNull, + reason: productField.offTag, + ); + expect( + tagsInLanguages, + isNotEmpty, + reason: productField.offTag, + ); + final int count = 3; // vegetarian, vegan, palm-oil + + for (final OpenFoodFactsLanguage language in languages) { + expect( + tagsInLanguages![language], + isNotNull, + reason: productField.offTag, + ); + expect( + tagsInLanguages[language]!.length, + count, + reason: productField.offTag, + ); + } + } + + check( + product.categoriesTags, + product.categoriesTagsInLanguages, + ProductField.CATEGORIES_TAGS_IN_LANGUAGES, + ); + check( + product.tracesTags, + product.tracesTagsInLanguages, + ProductField.TRACES_TAGS_IN_LANGUAGES, + ); + check( + product.storesTags, + product.storesTagsInLanguages, + ProductField.STORES_TAGS_IN_LANGUAGES, + ); + check( + product.statesTags, + product.statesTagsInLanguages, + ProductField.STATES_TAGS_IN_LANGUAGES, + ); + check( + product.brandsTags, + product.brandsTagsInLanguages, + ProductField.BRANDS_TAGS_IN_LANGUAGES, + ); + check( + product.miscTags, + product.miscTagsInLanguages, + ProductField.MISC_TAGS_IN_LANGUAGES, + ); + checkIngredientAnaysisTags( + product.ingredientsAnalysisTags, + product.ingredientsAnalysisTagsInLanguages, + ProductField.INGREDIENTS_ANALYSIS_TAGS_IN_LANGUAGES, + ); + check( + product.labelsTags, + product.labelsTagsInLanguages, + ProductField.LABELS_TAGS_IN_LANGUAGES, + ); + check( + product.countriesTags, + product.countriesTagsInLanguages, + ProductField.COUNTRIES_TAGS_IN_LANGUAGES, + ); + check( + product.ingredientsTags, + product.ingredientsTagsInLanguages, + ProductField.INGREDIENTS_TEXT_IN_LANGUAGES, + ); + }); + + test('get all "tags in languages" (Map)', () async { + const String barcode = '5449000000996'; + const List languages = [ + OpenFoodFactsLanguage.ENGLISH, + OpenFoodFactsLanguage.FRENCH, + ]; + + final ProductQueryConfiguration configurations = + ProductQueryConfiguration( + barcode, + languages: languages, + fields: [ + ProductField.IMAGES_FRESHNESS_IN_LANGUAGES, + ], + version: ProductQueryVersion.v3, + ); + final ProductResultV3 result = await OpenFoodAPIClient.getProductV3( + configurations, + ); + expect(result.status, ProductResultV3.statusSuccess); + expect(result.product, isNotNull); + final Product product = result.product!; + + void check( + final Map>? tagsInLanguages, + final ProductField productField, + ) { + expect( + tagsInLanguages, + isNotNull, + reason: productField.offTag, + ); + expect( + tagsInLanguages, + isNotEmpty, + reason: productField.offTag, + ); + + for (final OpenFoodFactsLanguage language in languages) { + expect( + tagsInLanguages![language], + isNotNull, + reason: productField.offTag, + ); + expect( + tagsInLanguages[language], + isNotEmpty, + reason: productField.offTag, + ); + } + } + + check( + product.imagesFreshnessInLanguages, + ProductField.IMAGES_FRESHNESS_IN_LANGUAGES, + ); + }); + + test('get all "tags all languages" (String)', () async { + const String barcode = '5449000000996'; + const List languages = [ + OpenFoodFactsLanguage.ENGLISH, + OpenFoodFactsLanguage.FRENCH, + ]; + + final ProductQueryConfiguration configurations = + ProductQueryConfiguration( + barcode, + languages: languages, + fields: [ + ProductField.NAME_IN_LANGUAGES, + ProductField.INGREDIENTS_TEXT_IN_LANGUAGES, + ProductField.PACKAGING_TEXT_IN_LANGUAGES, + ], + version: ProductQueryVersion.v3, + ); + final ProductResultV3 result = await OpenFoodAPIClient.getProductV3( + configurations, + ); + expect(result.status, ProductResultV3.statusSuccess); + expect(result.product, isNotNull); + final Product product = result.product!; + + void check( + final Map? tagsInLanguages, + final ProductField productField, + ) { + expect( + tagsInLanguages, + isNotNull, + reason: productField.offTag, + ); + expect( + tagsInLanguages, + isNotEmpty, + reason: productField.offTag, + ); + + for (final OpenFoodFactsLanguage language in languages) { + expect( + tagsInLanguages![language], + isNotNull, + reason: productField.offTag, + ); + expect( + tagsInLanguages[language], + isNotEmpty, + reason: productField.offTag, + ); + } + } + + check( + product.productNameInLanguages, + ProductField.NAME_IN_LANGUAGES, + ); + check( + product.ingredientsTextInLanguages, + ProductField.INGREDIENTS_TEXT_IN_LANGUAGES, + ); + check( + product.packagingTextInLanguages, + ProductField.PACKAGING_TEXT_IN_LANGUAGES, + ); + }); + + test('get all "tags in languages" (List)', () async { + const String barcode = '5449000000996'; + const List languages = [ + OpenFoodFactsLanguage.ENGLISH, + OpenFoodFactsLanguage.FRENCH, + ]; + + final ProductQueryConfiguration configurations = + ProductQueryConfiguration( + barcode, + languages: languages, + fields: [ + ProductField.INGREDIENTS_TEXT_ALL_LANGUAGES, + ProductField.PACKAGING_TEXT_ALL_LANGUAGES, + ProductField.NAME_ALL_LANGUAGES, + ProductField.INGREDIENTS_TEXT_IN_LANGUAGES, + ProductField.PACKAGING_TEXT_IN_LANGUAGES, + ProductField.NAME_IN_LANGUAGES, + ], + version: ProductQueryVersion.v3, + ); + final ProductResultV3 result = await OpenFoodAPIClient.getProductV3( + configurations, + ); + expect(result.status, ProductResultV3.statusSuccess); + expect(result.product, isNotNull); + final Product product = result.product!; + + void check( + final Map? tagsInLanguages, + final ProductField productField, + ) { + expect( + tagsInLanguages, + isNotNull, + reason: productField.offTag, + ); + expect( + tagsInLanguages, + isNotEmpty, + reason: productField.offTag, + ); + } + + check( + product.ingredientsTextInLanguages, + ProductField.INGREDIENTS_TEXT_ALL_LANGUAGES, + ); + check( + product.packagingTextInLanguages, + ProductField.PACKAGING_TEXT_ALL_LANGUAGES, + ); + check( + product.productNameInLanguages, + ProductField.NAME_ALL_LANGUAGES, + ); + }); + + test('get all "tags in+all languages" (List)', () async { + const String barcode = '5449000000996'; + + final ProductQueryConfiguration configurations = + ProductQueryConfiguration( + barcode, + fields: [ + // tags all languages + ProductField.INGREDIENTS_TEXT_ALL_LANGUAGES, + ProductField.PACKAGING_TEXT_ALL_LANGUAGES, + ProductField.NAME_ALL_LANGUAGES, + ], + version: ProductQueryVersion.v3, + ); + final ProductResultV3 result = await OpenFoodAPIClient.getProductV3( + configurations, + ); + expect(result.status, ProductResultV3.statusSuccess); + expect(result.product, isNotNull); + final Product product = result.product!; + + void check( + final Map? tagsInLanguages, + final ProductField productField, + ) { + expect( + tagsInLanguages, + isNotNull, + reason: productField.offTag, + ); + expect( + tagsInLanguages, + isNotEmpty, + reason: productField.offTag, + ); + } + + check( + product.ingredientsTextInLanguages, + ProductField.INGREDIENTS_TEXT_ALL_LANGUAGES, + ); + check( + product.packagingTextInLanguages, + ProductField.PACKAGING_TEXT_ALL_LANGUAGES, + ); + check( + product.productNameInLanguages, + ProductField.NAME_ALL_LANGUAGES, + ); + }); + }); +} diff --git a/test/api_get_product_test.dart b/test/api_get_product_test.dart index 0c5b8cfd6f..104d45916b 100644 --- a/test/api_get_product_test.dart +++ b/test/api_get_product_test.dart @@ -75,31 +75,6 @@ void main() { expect(result.product!.countries, 'United States'); }); - test('get packaging text in languages (Coca-Cola)', () async { - const String barcode = '5449000000996'; - const List languages = [ - OpenFoodFactsLanguage.ENGLISH, - OpenFoodFactsLanguage.FRENCH, - ]; - - final ProductQueryConfiguration configurations = - ProductQueryConfiguration( - barcode, - languages: languages, - fields: [ProductField.PACKAGING_TEXT_IN_LANGUAGES], - version: ProductQueryVersion.v3, - ); - final ProductResultV3 result = await OpenFoodAPIClient.getProductV3( - configurations, - ); - expect(result.status, ProductResultV3.statusSuccess); - expect(result.product, isNotNull); - expect(result.product!.packagingTextInLanguages, isNotNull); - for (final OpenFoodFactsLanguage language in languages) { - expect(result.product!.packagingTextInLanguages![language], isNotNull); - } - }); - test('check alcohol data', () async { const String barcode = '3119780259625'; @@ -930,36 +905,6 @@ void main() { invalidBarcodes.isBlacklisted(BARCODE_DANISH_BUTTER_COOKIES), isFalse); }); - test('get images freshness', () async { - final List languages = [ - OpenFoodFactsLanguage.ENGLISH, - OpenFoodFactsLanguage.RUSSIAN, - OpenFoodFactsLanguage.GERMAN, - OpenFoodFactsLanguage.FRENCH, - ]; - final ProductResultV3 productResult = await OpenFoodAPIClient.getProductV3( - ProductQueryConfiguration( - BARCODE_DANISH_BUTTER_COOKIES, - languages: languages, - fields: [ProductField.IMAGES_FRESHNESS_IN_LANGUAGES], - version: ProductQueryVersion.v3, - ), - ); - final Product product = productResult.product!; - const int TEN_YEARS = 10 * 365 * 24 * 3600; - for (final OpenFoodFactsLanguage language in languages) { - final Map freshnesses = - product.imagesFreshnessInLanguages![language]!; - for (final ImageField imageField in ImageField.values) { - final int? freshness = freshnesses[imageField]; - if (freshness != null) { - expect(freshness >= 0, isTrue); - expect(freshness < TEN_YEARS, isTrue); - } - } - } - }); - test('get product uri', () async { const String barcode = BARCODE_DANISH_BUTTER_COOKIES; OpenFoodAPIConfiguration.uuid = 'Should not appear in the url';