diff --git a/lib/src/model/product.dart b/lib/src/model/product.dart index e8ac415e8c..f29af095b3 100644 --- a/lib/src/model/product.dart +++ b/lib/src/model/product.dart @@ -196,10 +196,36 @@ class Product extends JsonObject { @JsonKey( name: 'images', includeIfNull: false, - fromJson: JsonHelper.imagesFromJson, - toJson: JsonHelper.imagesToJson) + fromJson: JsonHelper.allImagesFromJson, + toJson: JsonHelper.allImagesToJson) + + /// All images in bulk. Will include "main" images and "raw" images. + /// + /// See also [getRawImages] and [getMainImages]. List? images; + /// "Raw" (uploaded) images: for example "picture 12" resized to "400" size. + List? getRawImages() => _getImageSubset(false); + + /// "Main" images: the selected images for certain criteria. + /// + /// For example the "front" picture in "French" + /// Images may be returned in multiple sizes + List? getMainImages() => _getImageSubset(true); + + List? _getImageSubset(final bool isMain) { + if (images == null) { + return null; + } + final List result = []; + for (final ProductImage productImage in images!) { + if (productImage.isMain == isMain) { + result.add(productImage); + } + } + return result; + } + @JsonKey( name: 'ingredients', includeIfNull: false, diff --git a/lib/src/model/product.g.dart b/lib/src/model/product.g.dart index 0832908d74..a5955630b5 100644 --- a/lib/src/model/product.g.dart +++ b/lib/src/model/product.g.dart @@ -37,7 +37,7 @@ Product _$ProductFromJson(Map json) => Product( packagingQuantity: JsonHelper.quantityFromJson(json['product_quantity']), selectedImages: JsonHelper.selectedImagesFromJson(json['selected_images'] as Map?), - images: JsonHelper.imagesFromJson(json['images'] as Map?), + images: JsonHelper.allImagesFromJson(json['images'] as Map?), ingredients: (json['ingredients'] as List?) ?.map((e) => Ingredient.fromJson(e as Map)) .toList(), @@ -204,7 +204,7 @@ Map _$ProductToJson(Product instance) { writeNotNull('product_quantity', instance.packagingQuantity); val['selected_images'] = JsonHelper.selectedImagesToJson(instance.selectedImages); - val['images'] = JsonHelper.imagesToJson(instance.images); + val['images'] = JsonHelper.allImagesToJson(instance.images); writeNotNull( 'ingredients', JsonHelper.ingredientsToJson(instance.ingredients)); writeNotNull('ingredients_text', instance.ingredientsText); diff --git a/lib/src/model/product_image.dart b/lib/src/model/product_image.dart index a71f5b0079..6fad432448 100644 --- a/lib/src/model/product_image.dart +++ b/lib/src/model/product_image.dart @@ -1,5 +1,7 @@ import 'off_tagged.dart'; import '../utils/language_helper.dart'; +import '../utils/open_food_api_configuration.dart'; +import '../utils/uri_helper.dart'; enum ImageField implements OffTagged { FRONT(offTag: 'front'), @@ -95,13 +97,20 @@ extension ImageAngleExtension on ImageAngle { } } -/// The url to a specific product image. -/// Categorized by content type, size and language +/// Product image. Can be "main" (e.g. "front_fr") or "raw" (e.g. "picture #9"). +/// +/// For "main" images the key is field + language + size. +/// For "raw" images the key is the imgid + size. +/// +/// We have limited data for "raw" images, like "this is the nth image for this +/// product". +/// We have more data for "main" images, like "we built this image from that raw +/// image with this crop parameters and angle". class ProductImage { ProductImage({ - required this.field, + required ImageField this.field, this.size, - this.language, + required OpenFoodFactsLanguage this.language, this.url, this.rev, this.imgid, @@ -115,7 +124,16 @@ class ProductImage { this.height, }); - final ImageField field; + ProductImage.raw({ + this.size, + this.url, + required String this.imgid, + this.width, + this.height, + }) : language = null, + field = null; + + final ImageField? field; final ImageSize? size; final OpenFoodFactsLanguage? language; String? url; @@ -150,9 +168,66 @@ class ProductImage { /// Image height. int? height; + bool get isMain => field != null && language != null; + + /// Returns the url to display this image. + /// + /// cf. https://github.com/openfoodfacts/smooth-app/issues/3065 + String getUrl( + final String barcode, { + final ImageSize? imageSize, + final UriProductHelper uriHelper = uriHelperFoodProd, + }) => + '${uriHelper.getProductImageRootUrl(barcode)}' + '/' + '${getUrlFilename(imageSize: imageSize)}'; + + /// Returns just the filename of the url to display this image. + /// + /// See also [getUrl]. + String getUrlFilename({ + final ImageSize? imageSize, + }) { + final ImageSize bestImageSize = imageSize ?? size ?? ImageSize.UNKNOWN; + return isMain + ? _getMainUrlFilename(bestImageSize) + : _getRawUrlFilename(bestImageSize); + } + + /// Returns just the filename of the url to display this "main" image. + /// + /// By default uses the own [image]'s size field. + /// E.g. "front_fr.4.100.jpg" + /// cf. https://github.com/openfoodfacts/smooth-app/issues/3065 + String _getMainUrlFilename(final ImageSize imageSize) => + '${field!.offTag}_${language.code}' + '.$rev' + '.${imageSize.number}' + '.jpg'; + + /// Returns just the filename of the url to display this "raw" image. + /// + /// By default uses the own [image]'s size field. + /// E.g. "7.100.jpg" + String _getRawUrlFilename(final ImageSize imageSize) { + switch (imageSize) { + case ImageSize.THUMB: + case ImageSize.DISPLAY: + // adapted size + return '$imgid.${imageSize.number}.jpg'; + case ImageSize.SMALL: + // not available, we take the best other choice instead + return '$imgid.${ImageSize.DISPLAY.number}.jpg'; + case ImageSize.ORIGINAL: + case ImageSize.UNKNOWN: + // full size + return '$imgid.jpg'; + } + } + @override String toString() => 'ProductImage(' - '${field.offTag}' + '${field == null ? '' : 'field=${field!.offTag}'}' '${size == null ? '' : ',size=${size!.offTag}'}' '${language == null ? '' : ',language=${language.code}'}' '${angle == null ? '' : ',angle=${angle!.degreesClockwise}'}' @@ -168,9 +243,11 @@ class ProductImage { '${height == null ? '' : ',height=$height'}' ')'; + String get _key => + '${field?.offTag}_${language?.code}_${size?.offTag}_$imgid'; + @override - int get hashCode => - '${field.offTag}_${language?.code}_${size?.offTag}'.hashCode; + int get hashCode => _key.hashCode; @override bool operator ==(Object other) { diff --git a/lib/src/open_food_api_client.dart b/lib/src/open_food_api_client.dart index 2a0411eacc..49d934205f 100644 --- a/lib/src/open_food_api_client.dart +++ b/lib/src/open_food_api_client.dart @@ -309,6 +309,8 @@ class OpenFoodAPIClient { /// /// To be used in combination with [ImageHelper.getUploadedImageUrl]. /// Does not depend on language or country. + // TODO: deprecated from 2023-11-25; remove when old enough + @Deprecated('Use product field "images" instead') static Future> getProductImageIds( final String barcode, { final User? user, diff --git a/lib/src/utils/image_helper.dart b/lib/src/utils/image_helper.dart index 1ac75c51c3..455fa3ea6a 100644 --- a/lib/src/utils/image_helper.dart +++ b/lib/src/utils/image_helper.dart @@ -1,4 +1,3 @@ -import 'language_helper.dart'; import 'open_food_api_configuration.dart'; import 'uri_helper.dart'; import '../model/product_image.dart'; @@ -14,65 +13,56 @@ class ImageHelper { /// Returns the [image] full url - for a specific [imageSize] if needed. /// /// E.g. "https://images.openfoodfacts.org/images/products/359/671/046/2858/front_fr.4.100.jpg" + // TODO: deprecated from 2023-11-25; remove when old enough + @Deprecated('Use ProductImage.getUrl instead') static String getLocalizedProductImageUrl( final String barcode, final ProductImage image, { final ImageSize? imageSize, final UriProductHelper uriHelper = uriHelperFoodProd, }) => - '${uriHelper.getProductImageRootUrl(barcode)}' - '/' - '${getProductImageFilename( - image, - imageSize: imageSize, - )}'; + image.getUrl(barcode, uriHelper: uriHelper, imageSize: imageSize); /// Returns the [image] full url for an uploaded ("raw") image. /// /// E.g. "https://images.openfoodfacts.org/images/products/359/671/046/2858/1.400.jpg" + // TODO: deprecated from 2023-11-25; remove when old enough + @Deprecated('Use ProductImage.getUrl instead') static String getUploadedImageUrl( final String barcode, final int imageId, final ImageSize imageSize, { final UriProductHelper uriHelper = uriHelperFoodProd, }) => - '${uriHelper.getProductImageRootUrl(barcode)}' - '/' - '${getUploadedImageFilename(imageId, imageSize)}'; + ProductImage.raw(imgid: imageId.toString(), size: imageSize).getUrl( + barcode, + uriHelper: uriHelper, + ); /// Returns the [image] filename - for a specific [imageSize] if needed. /// /// By default uses the own [image]'s size field. /// E.g. "front_fr.4.100.jpg" /// cf. https://github.com/openfoodfacts/smooth-app/issues/3065 + // TODO: deprecated from 2023-11-25; remove when old enough + @Deprecated('Use ProductImage.getUrlFilename instead') static String getProductImageFilename( final ProductImage image, { final ImageSize? imageSize, }) => - '${image.field.offTag}_${image.language.code}' - '.${image.rev}' - '.${((imageSize ?? image.size) ?? ImageSize.UNKNOWN).number}' - '.jpg'; + image.getUrlFilename(imageSize: imageSize); /// Returns the filename of an uploaded image. /// /// cf. https://github.com/openfoodfacts/smooth-app/issues/3065 + // TODO: deprecated from 2023-11-25; remove when old enough + @Deprecated('Use ProductImage.getUrlFilename instead') static String getUploadedImageFilename( final int imageId, final ImageSize imageSize, - ) { - switch (imageSize) { - case ImageSize.THUMB: - case ImageSize.DISPLAY: - // adapted size - return '$imageId.${imageSize.number}.jpg'; - case ImageSize.SMALL: - // not available, we take the best other choice instead - return '$imageId.${ImageSize.DISPLAY.number}.jpg'; - case ImageSize.ORIGINAL: - case ImageSize.UNKNOWN: - // full size - return '$imageId.jpg'; - } - } + ) => + ProductImage.raw( + imgid: imageId.toString(), + size: imageSize, + ).getUrlFilename(); } diff --git a/lib/src/utils/json_helper.dart b/lib/src/utils/json_helper.dart index 3ca4cae3b9..67641a54e1 100644 --- a/lib/src/utils/json_helper.dart +++ b/lib/src/utils/json_helper.dart @@ -80,33 +80,47 @@ class JsonHelper { /// /// For historical reasons we keep only the 4 main images here, on all sizes /// and languages. - static List? imagesFromJson(Map? json) { + // TODO: deprecated from 2023-11-25; remove when old enough + @Deprecated('Use allImagesFromJson instead') + static List? imagesFromJson(Map? json) => allImagesFromJson( + json, + onlyMain: true, + ); + + /// Returns [ProductImage]s from a JSON map for "Images". + static List? allImagesFromJson( + Map? json, { + final bool onlyMain = false, + }) { if (json == null) return null; var imageList = []; for (final String key in json.keys) { + ImageField? field; + OpenFoodFactsLanguage? lang; final int? imageId = int.tryParse(key); if (imageId != null) { - // we can expect integer (imageIds) and String (field + language) - // here we ignore imageIds. - continue; - } - final List values = key.split('_'); - if (values.length != 2) { - // we expect field + '_' + language - continue; - } - final String fieldString = values[0]; - final ImageField? field = ImageField.fromOffTag(fieldString); - if (field == null) { - continue; - } - final String languageString = values[1]; - final OpenFoodFactsLanguage? lang = - OpenFoodFactsLanguage.fromOffTag(languageString); - if (lang == null) { - continue; + // the key is an int: it's a "raw" image + if (onlyMain) { + continue; + } + } else { + // we expect field + '_' + language: it's a "main" image + final List values = key.split('_'); + if (values.length != 2) { + continue; + } + final String fieldString = values[0]; + field = ImageField.fromOffTag(fieldString); + if (field == null) { + continue; + } + final String languageString = values[1]; + lang = OpenFoodFactsLanguage.fromOffTag(languageString); + if (lang == null) { + continue; + } } final Map fieldObject = json[key]; @@ -117,6 +131,27 @@ class JsonHelper { continue; } + if (imageId != null) { + // get each number object (e.g. 200) + for (var size in ImageSize.values) { + var number = size.number; + var numberObject = sizesObject[number] as Map?; + if (numberObject == null) { + continue; + } + imageList.add( + ProductImage.raw( + size: size, + imgid: imageId.toString(), + width: JsonObject.parseInt(numberObject['w']), + height: JsonObject.parseInt(numberObject['h']), + url: numberObject['url'], + ), + ); + } + continue; + } + final rev = JsonObject.parseInt(fieldObject['rev']); final String imgid = fieldObject['imgid'].toString(); final ImageAngle? angle = ImageAngleExtension.fromInt( @@ -141,9 +176,9 @@ class JsonHelper { final String? url = numberObject['url']; var image = ProductImage( - field: field, + field: field!, size: size, - language: lang, + language: lang!, rev: rev, imgid: imgid, angle: angle, @@ -163,20 +198,37 @@ class JsonHelper { return imageList; } - static Map imagesToJson(List? images) { + // TODO: deprecated from 2023-11-25; remove when old enough + @Deprecated('Use allImagesToJson instead') + static Map imagesToJson(List? images) => + allImagesToJson( + images, + onlyMain: true, + ); + + static Map allImagesToJson( + final List? images, { + final bool onlyMain = false, + }) { final Map result = {}; if (images == null || images.isEmpty) { return result; } - // grouped by "front_fr"-like keys + // grouped by "front_fr"-like or int keys final Map> sorted = >{}; for (final ProductImage productImage in images) { - if (productImage.language == null) { - continue; + final String key; + if (productImage.language != null && productImage.field != null) { + // it's a "main" image + key = '${productImage.field!.offTag}_${productImage.language!.offTag}'; + } else { + // it's a "raw" image + if (onlyMain) { + continue; + } + key = productImage.imgid!.toString(); } - final String key = - '${productImage.field.offTag}_${productImage.language!.offTag}'; List? items = sorted[key]; if (items == null) { items = []; @@ -211,29 +263,32 @@ class JsonHelper { item['sizes']![productImage.size!.number] = size; if (first) { first = false; - if (productImage.rev != null) { - item['rev'] = productImage.rev.toString(); - } - if (productImage.imgid != null) { - item['imgid'] = productImage.imgid!; - } - if (productImage.angle != null) { - item['angle'] = productImage.angle!.degree.toString(); - } - if (productImage.coordinatesImageSize != null) { - item['coordinates_image_size'] = productImage.coordinatesImageSize!; - } - if (productImage.x1 != null) { - item['x1'] = productImage.x1!; - } - if (productImage.y1 != null) { - item['y1'] = productImage.y1!; - } - if (productImage.x2 != null) { - item['x2'] = productImage.x2!; - } - if (productImage.y2 != null) { - item['y2'] = productImage.y2!; + if (productImage.isMain) { + if (productImage.rev != null) { + item['rev'] = productImage.rev.toString(); + } + if (productImage.imgid != null) { + item['imgid'] = productImage.imgid!; + } + if (productImage.angle != null) { + item['angle'] = productImage.angle!.degree.toString(); + } + if (productImage.coordinatesImageSize != null) { + item['coordinates_image_size'] = + productImage.coordinatesImageSize!; + } + if (productImage.x1 != null) { + item['x1'] = productImage.x1!; + } + if (productImage.y1 != null) { + item['y1'] = productImage.y1!; + } + if (productImage.x2 != null) { + item['x2'] = productImage.x2!; + } + if (productImage.y2 != null) { + item['y2'] = productImage.y2!; + } } } } diff --git a/lib/src/utils/product_helper.dart b/lib/src/utils/product_helper.dart index 68393d37c5..db3070bb80 100644 --- a/lib/src/utils/product_helper.dart +++ b/lib/src/utils/product_helper.dart @@ -1,4 +1,3 @@ -import 'image_helper.dart'; import 'language_helper.dart'; import 'uri_helper.dart'; import '../model/product.dart'; @@ -35,11 +34,7 @@ class ProductHelper { } for (ProductImage image in product.images!) { - image.url = ImageHelper.getLocalizedProductImageUrl( - product.barcode!, - image, - uriHelper: uriHelper, - ); + image.url = image.getUrl(product.barcode!, uriHelper: uriHelper); } } } diff --git a/test/api_get_product_image_ids_test.dart b/test/api_get_product_image_ids_test.dart index e05d57441c..4ba797f842 100644 --- a/test/api_get_product_image_ids_test.dart +++ b/test/api_get_product_image_ids_test.dart @@ -6,21 +6,24 @@ import 'test_constants.dart'; void main() { OpenFoodAPIConfiguration.userAgent = TestConstants.TEST_USER_AGENT; - test('get product image ids', () async { + test('get product images (all, main and raw)', () async { const String barcode = '3019081238643'; - final List result = await OpenFoodAPIClient.getProductImageIds( - barcode, + final ProductResultV3 result = await OpenFoodAPIClient.getProductV3( + ProductQueryConfiguration( + barcode, + version: ProductQueryVersion.v3, + fields: [ProductField.IMAGES], + ), user: TestConstants.PROD_USER, ); - expect(result.length, greaterThanOrEqualTo(34)); // was 34 on 2023-01-25 - }); - - test('get product image ids without any image', () async { - const String barcode = '98765432186'; - final List result = await OpenFoodAPIClient.getProductImageIds( - barcode, - user: TestConstants.PROD_USER, - ); - expect(result.length, greaterThanOrEqualTo(0)); + expect(result.product, isNotNull); + expect(result.product!.images, isNotNull); + final int countAll = result.product!.images!.length; + final int countRaw = result.product!.getRawImages()!.length; + final int countMain = result.product!.getMainImages()!.length; + expect(countRaw, greaterThanOrEqualTo(102)); // was 102 on 2023-11-25 + expect(countMain, greaterThanOrEqualTo(16)); // was 16 on 2023-11-25 + expect(countAll, greaterThanOrEqualTo(118)); // was 118 on 2023-11-25 + expect(countAll, countRaw + countMain); }); } diff --git a/test/api_get_product_test.dart b/test/api_get_product_test.dart index 104d45916b..eb509b28b6 100644 --- a/test/api_get_product_test.dart +++ b/test/api_get_product_test.dart @@ -657,26 +657,26 @@ void main() { test('get product without setting OpenFoodFactsLanguage or ProductField; ', () async { - String barcode = '5000112548167'; + const String barcode = '5000112548167'; + const int numberOfImages = 53; // was 53 in 20231125 //Get product without setting OpenFoodFactsLanguage or ProductField - ProductQueryConfiguration configurations = ProductQueryConfiguration( - barcode, - version: ProductQueryVersion.v3, - ); ProductResultV3 result = await OpenFoodAPIClient.getProductV3( - configurations, + ProductQueryConfiguration( + barcode, + version: ProductQueryVersion.v3, + ), ); expect(result.status, ProductResultV3.statusSuccess); expect(result.barcode, barcode); - expect(result.product != null, true); + expect(result.product, isNotNull); expect(result.product!.barcode, barcode); - expect(result.product!.lastModified != null, true); - expect(result.product!.ingredientsText != null, true); + expect(result.product!.lastModified, isNotNull); + expect(result.product!.ingredientsText, isNotNull); - expect(result.product!.ingredients != null, true); - expect(result.product!.ingredients!.length, 7); + expect(result.product!.ingredients, isNotNull); + expect(result.product!.ingredients, hasLength(7)); findExpectedIngredients(result.product!.ingredients!, ['Aroma']); expect(result.product!.additives!.ids[0], 'en:e150d'); @@ -692,8 +692,9 @@ void main() { result.product!.nutrientLevels!.levels[NutrientLevels.NUTRIENT_SALT], NutrientLevel.LOW); - expect(result.product!.images != null, true); - expect(result.product!.images!.length, 20); + expect(result.product!.images, isNotNull); + expect( + result.product!.images!.length, greaterThanOrEqualTo(numberOfImages)); expect( result.product!.images! .singleWhere((image) => @@ -704,13 +705,12 @@ void main() { 'https://images.openfoodfacts.org/images/products/500/011/254/8167/ingredients_de.7.400.jpg'); //Get product without setting ProductField - configurations = ProductQueryConfiguration( - barcode, - language: OpenFoodFactsLanguage.GERMAN, - version: ProductQueryVersion.v3, - ); result = await OpenFoodAPIClient.getProductV3( - configurations, + ProductQueryConfiguration( + barcode, + language: OpenFoodFactsLanguage.GERMAN, + version: ProductQueryVersion.v3, + ), ); expect(result.status, ProductResultV3.statusSuccess); @@ -722,7 +722,7 @@ void main() { expect(result.product!.ingredientsText, isNotNull); expect(result.product!.ingredients, isNotNull); - expect(result.product!.ingredients!.length, 7); + expect(result.product!.ingredients, hasLength(7)); findExpectedIngredients( result.product!.ingredients!, @@ -745,8 +745,9 @@ void main() { result.product!.nutrientLevels!.levels[NutrientLevels.NUTRIENT_SALT], NutrientLevel.LOW); - expect(result.product!.images != null, true); - expect(result.product!.images!.length, 20); + expect(result.product!.images, isNotNull); + expect( + result.product!.images!.length, greaterThanOrEqualTo(numberOfImages)); expect( result.product!.images! .singleWhere((image) => @@ -757,13 +758,12 @@ void main() { 'https://images.openfoodfacts.org/images/products/500/011/254/8167/ingredients_de.7.400.jpg'); //Get product without setting OpenFoodFactsLanguage - configurations = ProductQueryConfiguration( - barcode, - fields: [ProductField.ALL], - version: ProductQueryVersion.v3, - ); result = await OpenFoodAPIClient.getProductV3( - configurations, + ProductQueryConfiguration( + barcode, + fields: [ProductField.ALL], + version: ProductQueryVersion.v3, + ), ); expect(result.status, ProductResultV3.statusSuccess); @@ -775,7 +775,7 @@ void main() { expect(result.product!.ingredientsText, isNotNull); expect(result.product!.ingredients, isNotNull); - expect(result.product!.ingredients!.length, 7); + expect(result.product!.ingredients, hasLength(7)); findExpectedIngredients( result.product!.ingredients!, @@ -788,7 +788,7 @@ void main() { ], ); - expect(result.product!.selectedImages!.length, 15); + expect(result.product!.selectedImages, hasLength(15)); expect(result.product!.nutriments, isNotNull); final Nutriments nutriments = result.product!.nutriments!; @@ -820,7 +820,7 @@ void main() { NutrientLevel.LOW); expect(result.product!.images, isNotNull); - expect(result.product!.images!.length, 20); + expect(result.product!.images, hasLength(numberOfImages)); expect( result.product!.images! .singleWhere((image) => diff --git a/test/api_get_save_product_test.dart b/test/api_get_save_product_test.dart index 9b1585bacf..91fb1c88f6 100644 --- a/test/api_get_save_product_test.dart +++ b/test/api_get_save_product_test.dart @@ -120,7 +120,9 @@ void main() { expect(product.additives!.names[4], 'E950'); expect(product.images, isNotNull); - expect(product.images!.length, 4); + expect(product.images!, hasLength(7)); + expect(product.getRawImages(), hasLength(3)); + expect(product.getMainImages(), hasLength(4)); expect(product.countries, 'Frankreich,Deutschland'); }); diff --git a/test/api_json_to_from_test.dart b/test/api_json_to_from_test.dart index d803adbe2b..02ddd7b1cf 100644 --- a/test/api_json_to_from_test.dart +++ b/test/api_json_to_from_test.dart @@ -20,26 +20,58 @@ void main() { ); expect(productResult.product, isNotNull); - final List? images = productResult.product!.images; - expect(images, isNotNull); - expect(images, isNotEmpty); + void checkImages(final List? images) { + expect(images, isNotNull); + expect(images, isNotEmpty); - final List? imagesBackAndForth = JsonHelper.imagesFromJson( - JsonHelper.imagesToJson(images), - ); - expect(imagesBackAndForth, isNotNull); - expect(imagesBackAndForth, isNotEmpty); - - expect(imagesBackAndForth!.length, images!.length); - for (final ProductImage productImage1 in images) { - int count = 0; - for (final ProductImage productImage2 in imagesBackAndForth) { - if (productImage1 == productImage2) { - count++; + final List? imagesBackAndForth = + JsonHelper.allImagesFromJson( + JsonHelper.allImagesToJson(images), + ); + expect(imagesBackAndForth, isNotNull); + expect(imagesBackAndForth, isNotEmpty); + + expect(imagesBackAndForth!.length, images!.length); + for (final ProductImage productImage1 in images) { + int count = 0; + for (final ProductImage productImage2 in imagesBackAndForth) { + if (productImage1 == productImage2) { + count++; + } } + expect(count, 1); + } + } + + checkImages(productResult.product!.images); + checkImages(productResult.product!.getMainImages()); + checkImages(productResult.product!.getRawImages()); + + int countMain = 0; + int countRaw = 0; + for (final ProductImage productImage in productResult.product!.images!) { + if (productImage.isMain) { + countMain++; + } else { + countRaw++; } - expect(count, 1); } + + int count = 0; + for (final ProductImage productImage + in productResult.product!.getMainImages()!) { + expect(productImage.isMain, true); + count++; + } + expect(count, countMain); + + count = 0; + for (final ProductImage productImage + in productResult.product!.getRawImages()!) { + expect(productImage.isMain, false); + count++; + } + expect(count, countRaw); }); }); }