diff --git a/config/services/serializer.php b/config/services/serializer.php index a494c20..8136016 100644 --- a/config/services/serializer.php +++ b/config/services/serializer.php @@ -5,16 +5,27 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Webgriffe\SyliusElasticsearchPlugin\Serializer\ProductNormalizer; +use Webgriffe\SyliusElasticsearchPlugin\Serializer\ProductTranslationNormalizer; +use Webgriffe\SyliusElasticsearchPlugin\Serializer\ProductVariantNormalizer; return static function (ContainerConfigurator $containerConfigurator) { $services = $containerConfigurator->services(); $services->set('webgriffe.sylius_elasticsearch_plugin.serializer.product_normalizer', ProductNormalizer::class) + ->lazy() ->args([ service('sylius.product_variant_resolver.default'), service('event_dispatcher'), + service('serializer'), param('kernel.default_locale'), ]) ->tag('serializer.normalizer', ['priority' => 200]) ; + + $services->set('webgriffe.sylius_elasticsearch_plugin.serializer.product_variant_normalizer', ProductVariantNormalizer::class) + ->args([ + service('event_dispatcher'), + ]) + ->tag('serializer.normalizer', ['priority' => 200]) + ; }; diff --git a/src/Serializer/ProductNormalizer.php b/src/Serializer/ProductNormalizer.php index cf1d4c1..10b41a4 100644 --- a/src/Serializer/ProductNormalizer.php +++ b/src/Serializer/ProductNormalizer.php @@ -8,9 +8,7 @@ use Psr\EventDispatcher\EventDispatcherInterface; use RuntimeException; use Sylius\Component\Attribute\Model\AttributeValueInterface; -use Sylius\Component\Core\Model\CatalogPromotionInterface; use Sylius\Component\Core\Model\ChannelInterface; -use Sylius\Component\Core\Model\ChannelPricingInterface; use Sylius\Component\Core\Model\ProductImageInterface; use Sylius\Component\Core\Model\ProductInterface; use Sylius\Component\Core\Model\ProductTaxonInterface; @@ -24,17 +22,18 @@ use Sylius\Component\Product\Model\ProductOptionTranslationInterface; use Sylius\Component\Product\Model\ProductOptionValueInterface; use Sylius\Component\Product\Model\ProductOptionValueTranslationInterface; -use Sylius\Component\Product\Model\ProductVariantTranslationInterface; use Sylius\Component\Product\Resolver\ProductVariantResolverInterface; -use Sylius\Component\Promotion\Model\CatalogPromotionTranslationInterface; use Sylius\Component\Taxonomy\Model\TaxonTranslationInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\SerializerInterface; use Webgriffe\SyliusElasticsearchPlugin\Event\ProductDocumentType\ProductDocumentTypeProductNormalizeEvent; -use Webgriffe\SyliusElasticsearchPlugin\Event\ProductDocumentType\ProductDocumentTypeProductVariantNormalizeEvent; use Webgriffe\SyliusElasticsearchPlugin\Model\FilterableInterface; use Webmozart\Assert\Assert; -final class ProductNormalizer implements NormalizerInterface +/** + * @final + */ +class ProductNormalizer implements NormalizerInterface { /** @var string[] */ private array $localeCodes = []; @@ -47,6 +46,7 @@ final class ProductNormalizer implements NormalizerInterface public function __construct( private readonly ProductVariantResolverInterface $productVariantResolver, private readonly EventDispatcherInterface $eventDispatcher, + private readonly SerializerInterface&NormalizerInterface $serializer, private readonly string $systemDefaultLocaleCode, ) { } @@ -127,7 +127,7 @@ public function normalize(mixed $object, ?string $format = null, array $context } $defaultVariant = $this->productVariantResolver->getVariant($product); if ($defaultVariant instanceof ProductVariantInterface) { - $normalizedProduct['default-variant'] = $this->normalizeProductVariant($defaultVariant, $channel); + $normalizedProduct['default-variant'] = $this->serializer->normalize($defaultVariant, $format, $context); } $mainTaxon = $product->getMainTaxon(); if ($mainTaxon instanceof TaxonInterface) { @@ -138,7 +138,7 @@ public function normalize(mixed $object, ?string $format = null, array $context } /** @var ProductVariantInterface $variant */ foreach ($product->getVariants() as $variant) { - $normalizedProduct['variants'][] = $this->normalizeProductVariant($variant, $channel); + $normalizedProduct['variants'][] = $this->serializer->normalize($variant, $format, $context); } // Product attributes indexing for filters @@ -277,64 +277,6 @@ private function normalizeProductTaxon(ProductTaxonInterface $productTaxon): arr ); } - private function normalizeProductVariant(ProductVariantInterface $variant, ChannelInterface $channel): array - { - $normalizedVariant = [ - 'sylius-id' => $variant->getId(), - 'code' => $variant->getCode(), - 'enabled' => $variant->isEnabled(), - 'position' => $variant->getPosition(), - 'weight' => $variant->getWeight(), - 'width' => $variant->getWidth(), - 'height' => $variant->getHeight(), - 'depth' => $variant->getDepth(), - 'shipping-required' => $variant->isShippingRequired(), - 'name' => [], - 'on-hand' => $variant->getOnHand(), - 'on-hold' => $variant->getOnHold(), - 'is-tracked' => $variant->isTracked(), - 'price' => $this->normalizeChannelPricing($variant->getChannelPricingForChannel($channel)), - 'options' => [], - ]; - /** @var array $variantOptionsWithValue */ - $variantOptionsWithValue = []; - foreach ($variant->getOptionValues() as $optionValue) { - $option = $optionValue->getOption(); - Assert::isInstanceOf($option, ProductOptionInterface::class); - $optionId = $option->getId(); - if (!is_string($optionId) && !is_int($optionId)) { - throw new RuntimeException('Option ID different from string or integer is not supported.'); - } - if (array_key_exists($optionId, $variantOptionsWithValue)) { - throw new RuntimeException('Multiple values for the same option are not supported.'); - } - $variantOptionsWithValue[$optionId] = [ - 'option' => $option, - 'value' => $optionValue, - ]; - } - foreach ($variantOptionsWithValue as $optionAndValue) { - $normalizedVariant['options'][] = $this->normalizeProductOptionAndProductOptionValue( - $optionAndValue['option'], - $optionAndValue['value'], - ); - } - - /** @var ProductVariantTranslationInterface $variantTranslation */ - foreach ($variant->getTranslations() as $variantTranslation) { - $localeCode = $variantTranslation->getLocale(); - Assert::string($localeCode); - $normalizedVariant['name'][] = [ - $localeCode => $variantTranslation->getName(), - ]; - } - - $event = new ProductDocumentTypeProductVariantNormalizeEvent($variant, $channel, $normalizedVariant); - $this->eventDispatcher->dispatch($event); - - return $event->getNormalizedProductVariant(); - } - /** * @param array{attribute: ProductAttributeInterface, values: ProductAttributeValueInterface[]} $attributeWithValues */ @@ -479,69 +421,6 @@ private function normalizeOptionWithValues(array $optionWithValues): array return $normalizedOptionValue; } - private function normalizeProductOptionAndProductOptionValue( - ProductOptionInterface $option, - ProductOptionValueInterface $optionValue, - ): array { - $filterable = false; - if ($option instanceof FilterableInterface) { - $filterable = $option->isFilterable(); - } - $normalizedOption = [ - 'sylius-id' => $option->getId(), - 'code' => $option->getCode(), - 'name' => [], - 'filterable' => $filterable, - 'value' => $this->normalizeProductOptionValue($optionValue), - ]; - /** @var ProductOptionTranslationInterface $optionTranslation */ - foreach ($option->getTranslations() as $optionTranslation) { - $localeCode = $optionTranslation->getLocale(); - Assert::string($localeCode); - $normalizedOption['name'][] = [ - $localeCode => $optionTranslation->getName(), - ]; - } - - return $normalizedOption; - } - - private function normalizeChannelPricing(?ChannelPricingInterface $channelPricing): ?array - { - if ($channelPricing === null) { - return null; - } - $normalizedChannelPricing = [ - 'price' => $channelPricing->getPrice(), - 'original-price' => $channelPricing->getOriginalPrice(), - 'applied-promotions' => [], - ]; - /** @var CatalogPromotionInterface $catalogPromotion */ - foreach ($channelPricing->getAppliedPromotions() as $catalogPromotion) { - $normalizedCatalogPromotion = [ - 'sylius-id' => $catalogPromotion->getId(), - 'code' => $catalogPromotion->getCode(), - 'label' => [], - 'description' => [], - ]; - /** @var CatalogPromotionTranslationInterface $catalogPromotionTranslation */ - foreach ($catalogPromotion->getTranslations() as $catalogPromotionTranslation) { - $localeCode = $catalogPromotionTranslation->getLocale(); - Assert::string($localeCode); - $normalizedCatalogPromotion['label'][] = [ - $localeCode => $catalogPromotionTranslation->getLabel(), - ]; - $normalizedCatalogPromotion['description'][] = [ - $localeCode => $catalogPromotionTranslation->getDescription(), - ]; - } - - $normalizedChannelPricing['applied-promotions'][] = $normalizedCatalogPromotion; - } - - return $normalizedChannelPricing; - } - private function normalizeProductImage(ProductImageInterface $image): array { $normalizedImage = [ diff --git a/src/Serializer/ProductVariantNormalizer.php b/src/Serializer/ProductVariantNormalizer.php new file mode 100644 index 0000000..9c6d1e1 --- /dev/null +++ b/src/Serializer/ProductVariantNormalizer.php @@ -0,0 +1,194 @@ + $variant->getId(), + 'code' => $variant->getCode(), + 'enabled' => $variant->isEnabled(), + 'position' => $variant->getPosition(), + 'weight' => $variant->getWeight(), + 'width' => $variant->getWidth(), + 'height' => $variant->getHeight(), + 'depth' => $variant->getDepth(), + 'shipping-required' => $variant->isShippingRequired(), + 'name' => [], + 'on-hand' => $variant->getOnHand(), + 'on-hold' => $variant->getOnHold(), + 'is-tracked' => $variant->isTracked(), + 'price' => $this->normalizeChannelPricing($variant->getChannelPricingForChannel($channel)), + 'options' => [], + 'created-at' => $variant->getCreatedAt()?->format('c'), + ]; + /** @var array $variantOptionsWithValue */ + $variantOptionsWithValue = []; + foreach ($variant->getOptionValues() as $optionValue) { + $option = $optionValue->getOption(); + Assert::isInstanceOf($option, ProductOptionInterface::class); + $optionId = $option->getId(); + if (!is_string($optionId) && !is_int($optionId)) { + throw new \RuntimeException('Option ID different from string or integer is not supported.'); + } + if (array_key_exists($optionId, $variantOptionsWithValue)) { + throw new \RuntimeException('Multiple values for the same option are not supported.'); + } + $variantOptionsWithValue[$optionId] = [ + 'option' => $option, + 'value' => $optionValue, + ]; + } + foreach ($variantOptionsWithValue as $optionAndValue) { + $normalizedVariant['options'][] = $this->normalizeProductOptionAndProductOptionValue( + $optionAndValue['option'], + $optionAndValue['value'], + ); + } + + /** @var ProductVariantTranslationInterface $variantTranslation */ + foreach ($variant->getTranslations() as $variantTranslation) { + $localeCode = $variantTranslation->getLocale(); + Assert::string($localeCode); + $normalizedVariant['name'][] = [ + $localeCode => $variantTranslation->getName(), + ]; + } + + $event = new ProductDocumentTypeProductVariantNormalizeEvent($variant, $channel, $normalizedVariant); + $this->eventDispatcher->dispatch($event); + + return $event->getNormalizedProductVariant(); + } + + public function getSupportedTypes(?string $format): array + { + return [ProductVariantInterface::class => true]; + } + + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool + { + return $data instanceof ProductVariantInterface && + array_key_exists('type', $context) && + $context['type'] === 'webgriffe_sylius_elasticsearch_plugin' + ; + } + + private function normalizeChannelPricing(?ChannelPricingInterface $channelPricing): ?array + { + if ($channelPricing === null) { + return null; + } + $normalizedChannelPricing = [ + 'price' => $channelPricing->getPrice(), + 'original-price' => $channelPricing->getOriginalPrice(), + 'minimum-price' => $channelPricing->getMinimumPrice(), + 'lowest-price-before-discount' => $channelPricing->getLowestPriceBeforeDiscount(), + 'applied-promotions' => [], + ]; + /** @var CatalogPromotionInterface $catalogPromotion */ + foreach ($channelPricing->getAppliedPromotions() as $catalogPromotion) { + $normalizedCatalogPromotion = [ + 'sylius-id' => $catalogPromotion->getId(), + 'code' => $catalogPromotion->getCode(), + 'label' => [], + 'description' => [], + ]; + /** @var CatalogPromotionTranslationInterface $catalogPromotionTranslation */ + foreach ($catalogPromotion->getTranslations() as $catalogPromotionTranslation) { + $localeCode = $catalogPromotionTranslation->getLocale(); + Assert::string($localeCode); + $normalizedCatalogPromotion['label'][] = [ + $localeCode => $catalogPromotionTranslation->getLabel(), + ]; + $normalizedCatalogPromotion['description'][] = [ + $localeCode => $catalogPromotionTranslation->getDescription(), + ]; + } + + $normalizedChannelPricing['applied-promotions'][] = $normalizedCatalogPromotion; + } + + return $normalizedChannelPricing; + } + + private function normalizeProductOptionAndProductOptionValue( + ProductOptionInterface $option, + ProductOptionValueInterface $optionValue, + ): array { + $filterable = false; + if ($option instanceof FilterableInterface) { + $filterable = $option->isFilterable(); + } + $normalizedOption = [ + 'sylius-id' => $option->getId(), + 'code' => $option->getCode(), + 'name' => [], + 'filterable' => $filterable, + 'value' => $this->normalizeProductOptionValue($optionValue), + ]; + /** @var ProductOptionTranslationInterface $optionTranslation */ + foreach ($option->getTranslations() as $optionTranslation) { + $localeCode = $optionTranslation->getLocale(); + Assert::string($localeCode); + $normalizedOption['name'][] = [ + $localeCode => $optionTranslation->getName(), + ]; + } + + return $normalizedOption; + } + + private function normalizeProductOptionValue(ProductOptionValueInterface $optionValue): array + { + $normalizedOptionValue = [ + 'sylius-id' => $optionValue->getId(), + 'code' => $optionValue->getCode(), + 'value' => $optionValue->getValue(), + 'name' => [], + ]; + /** @var ProductOptionValueTranslationInterface $optionValueTranslation */ + foreach ($optionValue->getTranslations() as $optionValueTranslation) { + $localeCode = $optionValueTranslation->getLocale(); + Assert::string($localeCode); + $normalizedOptionValue['name'][] = [ + $localeCode => $optionValueTranslation->getValue(), + ]; + } + + return $normalizedOptionValue; + } +} diff --git a/tests/Unit/Serializer/ProductNormalizerTest.php b/tests/Unit/Serializer/ProductNormalizerTest.php index 784fa03..69a3d41 100644 --- a/tests/Unit/Serializer/ProductNormalizerTest.php +++ b/tests/Unit/Serializer/ProductNormalizerTest.php @@ -9,7 +9,9 @@ use Sylius\Component\Product\Resolver\DefaultProductVariantResolver; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Serializer; use Webgriffe\SyliusElasticsearchPlugin\Serializer\ProductNormalizer; +use Webgriffe\SyliusElasticsearchPlugin\Serializer\ProductVariantNormalizer; class ProductNormalizerTest extends TestCase { @@ -20,9 +22,11 @@ class ProductNormalizerTest extends TestCase protected function setUp(): void { + $eventDispatcher = new EventDispatcher(); $this->productNormalizer = new ProductNormalizer( new DefaultProductVariantResolver(), - new EventDispatcher(), + $eventDispatcher, + new Serializer([new ProductVariantNormalizer($eventDispatcher)]), 'en_US', ); diff --git a/tests/Unit/Serializer/ProductVariantNormalizerTest.php b/tests/Unit/Serializer/ProductVariantNormalizerTest.php new file mode 100644 index 0000000..04d28da --- /dev/null +++ b/tests/Unit/Serializer/ProductVariantNormalizerTest.php @@ -0,0 +1,269 @@ +productVariantNormalizer = new ProductVariantNormalizer( + new EventDispatcher(), + ); + + $this->productVariantToNormalize = new ProductVariant(); + $reflectionProductVariant = new \ReflectionClass(ProductVariant::class); + $productVariantIdProperty = $reflectionProductVariant->getProperty('id'); + + + $reflectionProductOption = new \ReflectionClass(ProductOption::class); + $productOptionIdProperty = $reflectionProductOption->getProperty('id'); + + $reflectionProductOptionValue = new \ReflectionClass(ProductOptionValue::class); + $productOptionValueIdProperty = $reflectionProductOptionValue->getProperty('id'); + + $productVariantIdProperty->setValue($this->productVariantToNormalize, 1); + $this->productVariantToNormalize->setCode('ABARTH_595_HOODIE'); + $this->productVariantToNormalize->setEnabled(true); + $this->productVariantToNormalize->setPosition(2); + $this->productVariantToNormalize->setWeight(0.43); + $this->productVariantToNormalize->setWidth(0.76); + $this->productVariantToNormalize->setHeight(0.89); + $this->productVariantToNormalize->setDepth(0.14); + $this->productVariantToNormalize->setShippingRequired(true); + $this->productVariantToNormalize->setOnHand(50); + $this->productVariantToNormalize->setOnHold(47); + $this->productVariantToNormalize->setTracked(true); + $this->productVariantToNormalize->setShippingRequired(true); + $this->productVariantToNormalize->setCreatedAt(new \DateTimeImmutable('2020-01-01 10:00:00', new \DateTimeZone('Europe/Rome'))); + + $productVariantEnglishTranslation = new ProductVariantTranslation(); + $productVariantEnglishTranslation->setLocale('en_US'); + $productVariantEnglishTranslation->setName('Abarth 595 Hoodie'); + $this->productVariantToNormalize->addTranslation($productVariantEnglishTranslation); + + $productVariantItalianTranslation = new ProductVariantTranslation(); + $productVariantItalianTranslation->setLocale('it_IT'); + $productVariantItalianTranslation->setName('Felpa Abarth 595'); + $this->productVariantToNormalize->addTranslation($productVariantItalianTranslation); + + $this->channel = new Channel(); + $this->channel->setCode('WEB'); + + $channelPricing = new ChannelPricing(); + $channelPricing->setPrice(1000); + $channelPricing->setOriginalPrice(2000); + $channelPricing->setMinimumPrice(500); + $channelPricing->setLowestPriceBeforeDiscount(1500); + $channelPricing->setProductVariant($this->productVariantToNormalize); + $channelPricing->setChannelCode($this->channel->getCode()); + $this->productVariantToNormalize->addChannelPricing($channelPricing); + + $sizeProductOption = new ProductOption(); + $productOptionIdProperty->setValue($sizeProductOption, 10); + $sizeProductOption->setCode('SIZE'); + $sizeProductOption->setFilterable(true); + + $sizeProductOptionTranslation = new ProductOptionTranslation(); + $sizeProductOptionTranslation->setLocale('en_US'); + $sizeProductOptionTranslation->setName('Size'); + $sizeProductOption->addTranslation($sizeProductOptionTranslation); + + $sizeProductOptionTranslation = new ProductOptionTranslation(); + $sizeProductOptionTranslation->setLocale('it_IT'); + $sizeProductOptionTranslation->setName('Taglia'); + $sizeProductOption->addTranslation($sizeProductOptionTranslation); + + $sizeProductOptionValue = new ProductOptionValue(); + $productOptionValueIdProperty->setValue($sizeProductOptionValue, 21); + $sizeProductOptionValue->setCode('S'); + $sizeProductOptionValue->setOption($sizeProductOption); + $sizeProductOptionValue->setCurrentLocale('it_IT'); + $sizeProductOptionValue->setCurrentLocale('en_US'); + + $sizeProductOptionValueTranslation = new ProductOptionValueTranslation(); + $sizeProductOptionValueTranslation->setLocale('en_US'); + $sizeProductOptionValueTranslation->setValue('Small'); + $sizeProductOptionValue->addTranslation($sizeProductOptionValueTranslation); + + $sizeProductOptionValueTranslation = new ProductOptionValueTranslation(); + $sizeProductOptionValueTranslation->setLocale('it_IT'); + $sizeProductOptionValueTranslation->setValue('Piccola'); + $sizeProductOptionValue->addTranslation($sizeProductOptionValueTranslation); + + $this->productVariantToNormalize->addOptionValue($sizeProductOptionValue); + + $colorProductOption = new ProductOption(); + $productOptionIdProperty->setValue($colorProductOption, 11); + $colorProductOption->setCode('COLOR'); + $colorProductOption->setFilterable(true); + + $colorProductOptionTranslation = new ProductOptionTranslation(); + $colorProductOptionTranslation->setLocale('en_US'); + $colorProductOptionTranslation->setName('Color'); + $colorProductOption->addTranslation($colorProductOptionTranslation); + + $colorProductOptionTranslation = new ProductOptionTranslation(); + $colorProductOptionTranslation->setLocale('it_IT'); + $colorProductOptionTranslation->setName('Colore'); + $colorProductOption->addTranslation($colorProductOptionTranslation); + + $colorProductOptionValue = new ProductOptionValue(); + $productOptionValueIdProperty->setValue($colorProductOptionValue, 23); + $colorProductOptionValue->setCode('RED'); + $colorProductOptionValue->setOption($colorProductOption); + $colorProductOptionValue->setCurrentLocale('it_IT'); + $colorProductOptionValue->setCurrentLocale('en_US'); + + $colorProductOptionValueTranslation = new ProductOptionValueTranslation(); + $colorProductOptionValueTranslation->setLocale('en_US'); + $colorProductOptionValueTranslation->setValue('Red'); + $colorProductOptionValue->addTranslation($colorProductOptionValueTranslation); + + $colorProductOptionValueTranslation = new ProductOptionValueTranslation(); + $colorProductOptionValueTranslation->setLocale('it_IT'); + $colorProductOptionValueTranslation->setValue('Rosso'); + $colorProductOptionValue->addTranslation($colorProductOptionValueTranslation); + + $this->productVariantToNormalize->addOptionValue($colorProductOptionValue); + } + + public function testItIsInstantiable(): void + { + $this->assertInstanceOf(ProductVariantNormalizer::class, $this->productVariantNormalizer); + } + + public function testItIsAnInstanceOfNormalizer(): void + { + $this->assertInstanceOf(NormalizerInterface::class, $this->productVariantNormalizer); + } + + public function testItSupportProductVariantInterfaceType(): void + { + $supportedTypes= $this->productVariantNormalizer->getSupportedTypes(null); + + $this->assertArrayHasKey(ProductVariantInterface::class, $supportedTypes); + $this->assertTrue($supportedTypes[ProductVariantInterface::class]); + } + + public function testItSupportNormalizationWithRightType(): void + { + $this->assertTrue($this->productVariantNormalizer->supportsNormalization($this->productVariantToNormalize, null, ['type' => 'webgriffe_sylius_elasticsearch_plugin'])); + } + + public function testItDoesNotSupportNormalizationWithRightType(): void + { + $this->assertFalse($this->productVariantNormalizer->supportsNormalization($this->productVariantToNormalize, null, ['type' => 'other'])); + } + + public function testItDoesNotSupportNormalizationWithoutType(): void + { + $this->assertFalse($this->productVariantNormalizer->supportsNormalization($this->productVariantToNormalize)); + } + + public function testItNormalizeProductVariant(): void + { + $productVariantNormalized = $this->productVariantNormalizer->normalize($this->productVariantToNormalize, null, ['channel' => $this->channel]); + $this->assertIsArray($productVariantNormalized); + + $this->assertEquals(1, $productVariantNormalized['sylius-id']); + $this->assertEquals('ABARTH_595_HOODIE', $productVariantNormalized['code']); + $this->assertEquals(true, $productVariantNormalized['enabled']); + $this->assertEquals(2, $productVariantNormalized['position']); + $this->assertEquals(0.43, $productVariantNormalized['weight']); + $this->assertEquals(0.76, $productVariantNormalized['width']); + $this->assertEquals(0.89, $productVariantNormalized['height']); + $this->assertEquals(0.14, $productVariantNormalized['depth']); + $this->assertTrue($productVariantNormalized['shipping-required']); + $this->assertEquals([ + [ + 'en_US' => 'Abarth 595 Hoodie', + ], + [ + 'it_IT' => 'Felpa Abarth 595', + ], + ], $productVariantNormalized['name']); + $this->assertEquals(50, $productVariantNormalized['on-hand']); + $this->assertEquals(47, $productVariantNormalized['on-hold']); + $this->assertTrue($productVariantNormalized['is-tracked']); + $this->assertEquals([ + 'price' => 1000, + 'original-price' => 2000, + 'minimum-price' => 500, + 'lowest-price-before-discount' => 1500, + 'applied-promotions' => [], + ], $productVariantNormalized['price']); + $this->assertEquals([ + [ + 'sylius-id' => 10, + 'code' => 'SIZE', + 'name' => [ + [ + 'en_US' => 'Size', + ], + [ + 'it_IT' => 'Taglia', + ], + ], + 'filterable' => true, + 'value' => [ + 'sylius-id' => 21, + 'code' => 'S', + 'value' => 'Small', + 'name' => [ + [ + 'en_US' => 'Small', + ], + [ + 'it_IT' => 'Piccola', + ], + ], + ], + ], + [ + 'sylius-id' => 11, + 'code' => 'COLOR', + 'name' => [ + [ + 'en_US' => 'Color', + ], + [ + 'it_IT' => 'Colore', + ], + ], + 'filterable' => true, + 'value' => [ + 'sylius-id' => 23, + 'code' => 'RED', + 'value' => 'Red', + 'name' => [ + [ + 'en_US' => 'Red', + ], + [ + 'it_IT' => 'Rosso', + ], + ], + ], + ], + ], $productVariantNormalized['options']); + $this->assertEquals('2020-01-01T10:00:00+01:00', $productVariantNormalized['created-at']); + } +}