minor #15639 [API][Admin] Finish covering product scenarios (NoResponseMate)

This PR was merged into the 1.13 branch.

Discussion
----------

| Q               | A                                                            |
|-----------------|--------------------------------------------------------------|
| Branch?         | 1.13 |
| Bug fix?        | no                                                       |
| New feature?    | kinda                                                       |
| BC breaks?      | no                                                       |
| Deprecations?   | no |
| Related tickets | -                      |
| License         | MIT                                                          |

Commits
-------
  [API][Admin] Finish covering product filtering
  [API][Admin] Finish covering product and product taxon sorting
  [API][Admin] More product related scenarios marked no-api
  [API][Admin] Cover seeing applied promotions on variants
  [API][Admin] Refactor viewing product suite split
  [API] Finish covering viewing product attributes
  [API][Admin] Cover managing product associations
  Cleanup
  [API] Cover viewing details of a product
  [Behat] Update step names in scenarios after changing name of steps
This commit is contained in:
Jacob Tobiasz 2023-12-20 18:48:40 +01:00 committed by GitHub
commit 249eb7476f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 1565 additions and 251 deletions

View file

@ -110,7 +110,6 @@
* `Sylius\Bundle\ApiBundle\Filter\Doctrine\CatalogPromotionChannelFilter` * `Sylius\Bundle\ApiBundle\Filter\Doctrine\CatalogPromotionChannelFilter`
* `Sylius\Bundle\ApiBundle\Filter\Doctrine\ProductVariantCatalogPromotionFilter` * `Sylius\Bundle\ApiBundle\Filter\Doctrine\ProductVariantCatalogPromotionFilter`
* `Sylius\Bundle\ApiBundle\Filter\Doctrine\ProductVariantOptionValueFilter` * `Sylius\Bundle\ApiBundle\Filter\Doctrine\ProductVariantOptionValueFilter`
* `Sylius\Bundle\ApiBundle\Filter\Doctrine\ProductVariantCatalogPromotionFilter`
* `Sylius\Bundle\ApiBundle\Filter\Doctrine\ProductVariantOptionValueFilter` * `Sylius\Bundle\ApiBundle\Filter\Doctrine\ProductVariantOptionValueFilter`
* `Sylius\Bundle\ApiBundle\Filter\Doctrine\TaxonFilter` * `Sylius\Bundle\ApiBundle\Filter\Doctrine\TaxonFilter`
* `Sylius\Bundle\ApiBundle\Serializer\ChannelPriceHistoryConfigDenormalizer` * `Sylius\Bundle\ApiBundle\Serializer\ChannelPriceHistoryConfigDenormalizer`
@ -118,6 +117,10 @@
* `Sylius\Bundle\ApiBundle\Serializer\ProductVariantNormalizer` * `Sylius\Bundle\ApiBundle\Serializer\ProductVariantNormalizer`
* `Sylius\Bundle\ApiBundle\Serializer\ZoneDenormalizer` * `Sylius\Bundle\ApiBundle\Serializer\ZoneDenormalizer`
1. The `Sylius\Bundle\ApiBundle\Filter\Doctrine\CatalogPromotionChannelFilter` service and class has been renamed to `Sylius\Bundle\ApiBundle\Filter\Doctrine\ChannelsAwareChannelFilter`.
1. The `sylius.api.product_taxon_filter` filter has been removed and its functionality has been superseded by the `sylius.api.search_filter.taxon.code` filter. The usage stays the same.
1. Update in Translations Handling 1. Update in Translations Handling
The process for creating or updating translations via the API has been refined. Now, the locale for each translation The process for creating or updating translations via the API has been refined. Now, the locale for each translation

View file

@ -9,10 +9,16 @@ Feature: Adding associations to an existing product
And the store has "LG G3", "LG headphones" and "LG earphones" products And the store has "LG G3", "LG headphones" and "LG earphones" products
And I am logged in as an administrator And I am logged in as an administrator
@ui @javascript @ui @javascript @no-api
Scenario: Adding an association to an existing product Scenario: Adding an association to an existing product
When I want to modify the "LG G3" product When I want to modify the "LG G3" product
And I associate as "Accessories" the "LG headphones" and "LG earphones" products And I associate as "Accessories" the "LG headphones" and "LG earphones" products
And I save my changes And I save my changes
Then I should be notified that it has been successfully edited Then I should be notified that it has been successfully edited
And this product should have an association "Accessories" with products "LG headphones" and "LG earphones" And this product should have an association "Accessories" with products "LG headphones" and "LG earphones"
@api @no-ui
Scenario: Adding an association to na existing product
When I associate as "Accessories" the product "LG G3" with the products "LG headphones" and "LG earphones"
And this product should have an association "Accessories"
And this association should have products "LG headphones" and "LG earphones"

View file

@ -38,7 +38,7 @@ Feature: Adding a new product with associations
And this product should not have an association "Accessories" with product "LG earphones" And this product should not have an association "Accessories" with product "LG earphones"
And the product "LG G3" should appear in the store And the product "LG G3" should appear in the store
@ui @javascript @ui @javascript @no-api
Scenario: Adding a new product with association with numeric code Scenario: Adding a new product with association with numeric code
Given the store has 123 product association type Given the store has 123 product association type
When I want to create a new simple product When I want to create a new simple product

View file

@ -9,7 +9,7 @@ Feature: Changing associations of an existing product
And the store has "LG G3", "LG headphones" and "LG earphones" products And the store has "LG G3", "LG headphones" and "LG earphones" products
And I am logged in as an administrator And I am logged in as an administrator
@ui @javascript @ui @javascript @no-api
Scenario: Changing associated products of a product association Scenario: Changing associated products of a product association
Given the product "LG G3" has an association "Accessories" with product "LG headphones" Given the product "LG G3" has an association "Accessories" with product "LG headphones"
When I want to modify the "LG G3" product When I want to modify the "LG G3" product
@ -18,7 +18,19 @@ Feature: Changing associations of an existing product
Then I should be notified that it has been successfully edited Then I should be notified that it has been successfully edited
And this product should have an association "Accessories" with products "LG headphones" and "LG earphones" And this product should have an association "Accessories" with products "LG headphones" and "LG earphones"
@ui @javascript @api @no-ui
Scenario: Adding another product to a product association
Given the product "LG G3" has an association "Accessories" with product "LG headphones"
When I add the product "LG earphones" to this product association
Then this association should have products "LG headphones" and "LG earphones"
@api @no-ui
Scenario: Changing a product of a product association
Given the product "LG G3" has an association "Accessories" with product "LG headphones"
When I change this product association's product to the "LG earphones" product
Then this association should only have product "LG earphones"
@ui @javascript @no-api
Scenario: Removing an associated product of a product association Scenario: Removing an associated product of a product association
Given the product "LG G3" has an association "Accessories" with products "LG headphones" and "LG earphones" Given the product "LG G3" has an association "Accessories" with products "LG headphones" and "LG earphones"
When I want to modify the "LG G3" product When I want to modify the "LG G3" product
@ -27,3 +39,9 @@ Feature: Changing associations of an existing product
Then I should be notified that it has been successfully edited Then I should be notified that it has been successfully edited
And this product should have an association "Accessories" with product "LG headphones" And this product should have an association "Accessories" with product "LG headphones"
And this product should not have an association "Accessories" with product "LG earphones" And this product should not have an association "Accessories" with product "LG earphones"
@api @no-ui
Scenario: Removing a product of a product association
Given the product "LG G3" has an association "Accessories" with products "LG headphones" and "LG earphones"
When I remove the product "LG earphones" from this product association
Then this association should only have product "LG headphones"

View file

@ -12,7 +12,7 @@ Feature: Filtering products by a channel
And the store also has a product "HP Spectre" in channel "Web-US" And the store also has a product "HP Spectre" in channel "Web-US"
And I am logged in as an administrator And I am logged in as an administrator
@ui @ui @api
Scenario: Filtering products by a chosen channel Scenario: Filtering products by a chosen channel
When I browse products When I browse products
And I choose "Web-EU" as a channel filter And I choose "Web-EU" as a channel filter
@ -22,7 +22,7 @@ Feature: Filtering products by a channel
And I should see a product with name "MacBook Pro" And I should see a product with name "MacBook Pro"
But I should not see any product with name "HP Spectre" But I should not see any product with name "HP Spectre"
@ui @ui @api
Scenario: Filtering products by a chosen channel Scenario: Filtering products by a chosen channel
When I browse products When I browse products
And I choose "Web-US" as a channel filter And I choose "Web-US" as a channel filter

View file

@ -65,7 +65,7 @@ Feature: Sorting listed products
Then I should see 3 products in the list Then I should see 3 products in the list
And the first product on the list should have name "Szałowy Mops" And the first product on the list should have name "Szałowy Mops"
@ui @no-postgres @ui @api @no-postgres
Scenario: Missing translations are sorted as first when sorting by name ascending Scenario: Missing translations are sorted as first when sorting by name ascending
When I change my locale to "Polish" When I change my locale to "Polish"
And I browse products And I browse products
@ -74,7 +74,7 @@ Feature: Sorting listed products
And the first product on the list shouldn't have a name And the first product on the list shouldn't have a name
And the last product on the list should have name "Ekstremalny Mops" And the last product on the list should have name "Ekstremalny Mops"
@ui @no-postgres @ui @api @no-postgres
Scenario: Missing translation are sorted as last when sorting by name descending Scenario: Missing translation are sorted as last when sorting by name descending
When I change my locale to "Polish" When I change my locale to "Polish"
And I browse products And I browse products

View file

@ -5,7 +5,8 @@ Feature: Sorting listed products from a taxon by position
I want to sort products from a taxon by their positions I want to sort products from a taxon by their positions
Background: Background:
Given the store classifies its products as "Soft Toys" Given the store operates on a single channel in "United States"
And the store classifies its products as "Soft Toys"
And the store has a product "Old pug" in the "Soft Toys" taxon at 0th position And the store has a product "Old pug" in the "Soft Toys" taxon at 0th position
And the store has a product "Young pug" in the "Soft Toys" taxon at 1st position And the store has a product "Young pug" in the "Soft Toys" taxon at 1st position
And the store has a product "Small pug" in the "Soft Toys" taxon at 2nd position And the store has a product "Small pug" in the "Soft Toys" taxon at 2nd position
@ -28,7 +29,7 @@ Feature: Sorting listed products from a taxon by position
And the store has a product "Ultimate Pug" in the "Soft Toys" taxon at 19th position And the store has a product "Ultimate Pug" in the "Soft Toys" taxon at 19th position
And I am logged in as an administrator And I am logged in as an administrator
@ui @ui @no-api
Scenario: Setting two products to position -1 on the non-last page Scenario: Setting two products to position -1 on the non-last page
When I am browsing the 1st page of products from "Soft Toys" taxon When I am browsing the 1st page of products from "Soft Toys" taxon
And I set the position of "Old pug" to "-1" And I set the position of "Old pug" to "-1"
@ -38,7 +39,7 @@ Feature: Sorting listed products from a taxon by position
Then the one before last product on the list should have name "Old pug" with position 18 Then the one before last product on the list should have name "Old pug" with position 18
And the last product on the list should have name "Young pug" with position 19 And the last product on the list should have name "Young pug" with position 19
@ui @ui @no-api
Scenario: Setting two products to position -1 on the last page Scenario: Setting two products to position -1 on the last page
When I am browsing the 2nd page of products from "Soft Toys" taxon When I am browsing the 2nd page of products from "Soft Toys" taxon
And I set the position of "Pug XL" to "-1" And I set the position of "Pug XL" to "-1"
@ -47,7 +48,7 @@ Feature: Sorting listed products from a taxon by position
Then the one before last product on the list should have name "Pug XL" with position 18 Then the one before last product on the list should have name "Pug XL" with position 18
And the last product on the list should have name "Pug XS" with position 19 And the last product on the list should have name "Pug XS" with position 19
@ui @ui @no-api
Scenario: Setting two products to the already occupied position on the other page Scenario: Setting two products to the already occupied position on the other page
When I am browsing the 1st page of products from "Soft Toys" taxon When I am browsing the 1st page of products from "Soft Toys" taxon
And I set the position of "Old pug" to "15" And I set the position of "Old pug" to "15"
@ -63,7 +64,7 @@ Feature: Sorting listed products from a taxon by position
And the 7th product on this page should be named "Pug Master" And the 7th product on this page should be named "Pug Master"
And this product should be at position 16 And this product should be at position 16
@ui @ui @no-api
Scenario: Setting two products to the already occupied position on the same page Scenario: Setting two products to the already occupied position on the same page
When I am browsing the 2nd page of products from "Soft Toys" taxon When I am browsing the 2nd page of products from "Soft Toys" taxon
And I set the position of "Puglet" to "15" And I set the position of "Puglet" to "15"
@ -79,7 +80,7 @@ Feature: Sorting listed products from a taxon by position
And the 7th product on this page should be named "Pug Master" And the 7th product on this page should be named "Pug Master"
And this product should be at position 16 And this product should be at position 16
@ui @ui @no-api
Scenario: Setting two products to the positions overflowing the max available position on the non-last page Scenario: Setting two products to the positions overflowing the max available position on the non-last page
When I am browsing the 1st page of products from "Soft Toys" taxon When I am browsing the 1st page of products from "Soft Toys" taxon
And I set the position of "Old pug" to "25" And I set the position of "Old pug" to "25"
@ -89,7 +90,7 @@ Feature: Sorting listed products from a taxon by position
Then the one before last product on the list should have name "Old pug" with position 18 Then the one before last product on the list should have name "Old pug" with position 18
And the last product on the list should have name "Young pug" with position 19 And the last product on the list should have name "Young pug" with position 19
@ui @ui @no-api
Scenario: Setting two products to the positions overflowing the max available position on the last page Scenario: Setting two products to the positions overflowing the max available position on the last page
When I am browsing the 2nd page of products from "Soft Toys" taxon When I am browsing the 2nd page of products from "Soft Toys" taxon
And I set the position of "Puglet" to "25" And I set the position of "Puglet" to "25"
@ -99,33 +100,35 @@ Feature: Sorting listed products from a taxon by position
Then the one before last product on the list should have name "Puglet" with position 18 Then the one before last product on the list should have name "Puglet" with position 18
And the last product on the list should have name "Pug XL" with position 19 And the last product on the list should have name "Pug XL" with position 19
@ui @ui @api
Scenario: New product is added as last one Scenario: New product is added as last one
Given I added a product "Big pug" Given I added a product "Big pug"
And I assigned this product to "Soft Toys" taxon And I assigned this product to "Soft Toys" taxon
When I am browsing the 3rd page of products from "Soft Toys" taxon When I am browsing the 3rd page of products from "Soft Toys" taxon
Then the last product on the list should have name "Big pug" Then the last product on the list within this taxon should have name "Big pug"
@ui @ui @api
Scenario: Product with position 0 is set as the first one Scenario: Product with position 0 is set as the first one
When I am browsing products from "Soft Toys" taxon When I am browsing products from "Soft Toys" taxon
And I set the position of "Young pug" to 0 And I set the position of "Young pug" to 0
And I save my new configuration And I save my new configuration
Then the first product on the list should have name "Young pug" And I go to the 1st page of products from "Soft Toys" taxon
Then the first product on the list within this taxon should have name "Young pug"
@ui @ui @api
Scenario: Being unable to use a non-numeric string as a product position Scenario: Being unable to use a non-numeric string as a product position
Given I am browsing products from "Soft Toys" taxon Given I am browsing products from "Soft Toys" taxon
When I set the position of "Young pug" to "test" When I set the position of "Young pug" to "test"
And I save my new configuration And I save my new configuration
Then I should be notified that the position "test" is invalid Then I should be notified that the position "test" is invalid
@ui @ui @api
Scenario: Sort products in descending order Scenario: Sort products in descending order
When I am browsing products from "Soft Toys" taxon When I am browsing products from "Soft Toys" taxon
And I start sorting products by position And I sort this taxon's products "descending" by "position"
Then the first product on the list should have name "Ultimate Pug" Then the first product on the list within this taxon should have name "Ultimate Pug"
@ui
@ui @api
Scenario: Products are sorted by position in ascending order by default Scenario: Products are sorted by position in ascending order by default
When I am browsing products from "Soft Toys" taxon When I am browsing products from "Soft Toys" taxon
Then the first product on the list should have name "Old pug" Then the first product on the list within this taxon should have name "Old pug"

View file

@ -14,6 +14,6 @@ Feature: Accessing the price history from the configurable product show page
@ui @no-api @ui @no-api
Scenario: Being able to access the price history of variant from the configurable product show page Scenario: Being able to access the price history of variant from the configurable product show page
Given I am browsing products Given I am browsing products
When I access "Wyborowa Vodka" product page When I access the "Wyborowa Vodka" product
And I access the price history of a product variant "Wyborowa Vodka Exquisite" for "United States" channel And I access the price history of a product variant "Wyborowa Vodka Exquisite" for "United States" channel
Then I should see 1 log entries in the catalog price history Then I should see 1 log entries in the catalog price history

View file

@ -17,6 +17,6 @@ Feature: Accessing the price history from the simple product show page
@ui @no-api @ui @no-api
Scenario: Being able to access price history from simple product show page Scenario: Being able to access price history from simple product show page
Given I am browsing products Given I am browsing products
When I access "Ursus C-355" product page When I access the "Ursus C-355" product
And I access the price history of a simple product for "United States" channel And I access the price history of a simple product for "United States" channel
Then I should see 2 log entries in the catalog price history Then I should see 2 log entries in the catalog price history

View file

@ -12,14 +12,14 @@ Feature: Accessing to product edit page from show page
And I am logged in as an administrator And I am logged in as an administrator
And I am browsing products And I am browsing products
@ui @ui @no-api
Scenario: Accessing to product edit page from product show page Scenario: Accessing to product edit page from product show page
When I access "Iron shield" product page When I access the "Iron Shield" product
And I go to edit page And I go to edit page
Then I should be on "Iron shield" product edit page Then I should be on "Iron shield" product edit page
@ui @ui @no-api
Scenario: Accessing to variant edit page from product show page Scenario: Accessing to variant edit page from product show page
When I access "Iron shield" product page When I access the "Iron Shield" product
And I go to edit page of "Iron shield - very big" variant And I go to edit page of "Iron shield - very big" variant
Then I should be on "Iron shield - very big" variant edit page Then I should be on "Iron shield - very big" variant edit page

View file

@ -1,4 +1,4 @@
@viewing_products @viewing_product_in_admin_panel
Feature: Checking products in the shop while viewing them Feature: Checking products in the shop while viewing them
In order to check a product in shop in all channels it is available in In order to check a product in shop in all channels it is available in
As an Administrator As an Administrator
@ -14,18 +14,18 @@ Feature: Checking products in the shop while viewing them
@ui @no-api @ui @no-api
Scenario: Accessing product show page in shop from the product show page where product is available in more than one channel Scenario: Accessing product show page in shop from the product show page where product is available in more than one channel
Given this product is available in the "Europe" channel Given this product is available in the "Europe" channel
When I access "Bugatti" product page When I access the "Bugatti" product
And I show this product in the "Europe" channel And I show this product in the "Europe" channel
Then I should see this product in the "Europe" channel in the shop Then I should see this product in the "Europe" channel in the shop
@ui @no-api @ui @no-api
Scenario: Accessing product show page in shop from the product show page where product is available in one channel Scenario: Accessing product show page in shop from the product show page where product is available in one channel
When I access "Bugatti" product page When I access the "Bugatti" product
And I show this product in this channel And I show this product in this channel
Then I should see this product in the "Europe" channel in the shop Then I should see this product in the "Europe" channel in the shop
@ui @no-api @ui @no-api
Scenario: Being unable to access product show page in shop when the product is disabled Scenario: Being unable to access product show page in shop when the product is disabled
Given this product has been disabled Given this product has been disabled
When I access "Bugatti" product page When I access the "Bugatti" product
Then I should not be able to show this product in shop Then I should not be able to show this product in shop

View file

@ -1,4 +1,4 @@
@viewing_products @viewing_product_in_admin_panel
Feature: Seeing applied catalog promotions details for a simple product Feature: Seeing applied catalog promotions details for a simple product
In order to be aware of simple product price change reason In order to be aware of simple product price change reason
As an Administrator As an Administrator
@ -13,8 +13,9 @@ Feature: Seeing applied catalog promotions details for a simple product
And it reduces price by "90%" And it reduces price by "90%"
And it is enabled And it is enabled
And I am logged in as an administrator And I am logged in as an administrator
And I am browsing products
@ui @ui @no-api
Scenario: Seeing applied catalog promotion details on a simple product Scenario: Seeing applied catalog promotion details on a simple product
When I access "Ursus C-355" product When I access "Ursus C-355" product
Then this product price should be decreased by catalog promotion "Company bankruptcy sale" in "United States" channel Then this product price should be decreased by catalog promotion "Company bankruptcy sale" in "United States" channel

View file

@ -1,4 +1,4 @@
@viewing_products @viewing_product_in_admin_panel
Feature: Seeing applied catalog promotions details within variant Feature: Seeing applied catalog promotions details within variant
In order to be aware of variant's price change reason In order to be aware of variant's price change reason
As an Administrator As an Administrator
@ -20,11 +20,20 @@ Feature: Seeing applied catalog promotions details within variant
And it reduces price by "37%" And it reduces price by "37%"
And it is enabled And it is enabled
And I am logged in as an administrator And I am logged in as an administrator
And I am browsing products
@ui @ui @no-api
Scenario: Seeing applied catalog promotion details within variant Scenario: Seeing applied catalog promotion details within variant
When I access "Wyborowa Vodka" product When I access "Wyborowa Vodka" product
Then "Wyborowa Vodka Exquisite" variant price should be decreased by catalog promotion "Winter sale" in "United States" channel Then "Wyborowa Vodka Exquisite" variant price should be decreased by catalog promotion "Winter sale" in "United States" channel
And "Wyborowa Vodka Lemon" variant price should not be decreased by catalog promotion "Winter sale" in "United States" channel And "Wyborowa Vodka Lemon" variant price should not be decreased by catalog promotion "Winter sale" in "United States" channel
And "Wyborowa Vodka Exquisite" variant price should not be decreased by catalog promotion "Christmas sale" in "United States" channel And "Wyborowa Vodka Exquisite" variant price should not be decreased by catalog promotion "Christmas sale" in "United States" channel
And "Wyborowa Vodka Lemon" variant price should be decreased by catalog promotion "Christmas sale" in "United States" channel And "Wyborowa Vodka Lemon" variant price should be decreased by catalog promotion "Christmas sale" in "United States" channel
@api @no-ui
Scenario: Seeing applied catalog promotion details within variant
When I view all variants of the product "Wyborowa Vodka"
Then "Wyborowa Vodka Exquisite" variant price should be decreased by catalog promotion "Winter sale" in "United States" channel
And "Wyborowa Vodka Lemon" variant price should not be decreased by catalog promotion "Winter sale" in "United States" channel
And "Wyborowa Vodka Exquisite" variant price should not be decreased by catalog promotion "Christmas sale" in "United States" channel
And "Wyborowa Vodka Lemon" variant price should be decreased by catalog promotion "Christmas sale" in "United States" channel

View file

@ -17,55 +17,69 @@ Feature: Viewing details of a product with variants
And I am logged in as an administrator And I am logged in as an administrator
And I am browsing products And I am browsing products
@ui @ui @no-api
Scenario: Viewing a configurable product show page Scenario: Viewing a configurable product
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see product show page with variants Then I should see product show page with variants
And I should see product name "Iron Shield" And I should see product name "Iron Shield"
@ui @ui @api
Scenario: Viewing taxonomy block Scenario: Viewing taxonomies
Given the store classifies its products as "Shield" and "Equipment" Given the store classifies its products as "Shield" and "Equipment"
And the product "Iron Shield" has a main taxon "Equipment" And the product "Iron Shield" has a main taxon "Equipment"
And the product "Iron Shield" belongs to taxon "Shield" And the product "Iron Shield" belongs to taxon "Shield"
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see main taxon is "Equipment" Then I should see main taxon is "Equipment"
And I should see product taxon is "Shield" And I should see product taxon "Shield"
@ui @ui @api
Scenario: Viewing options block Scenario: Viewing options
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see option "Shield size" Then I should see option "Shield size"
@ui @ui @api
Scenario: Viewing variants block Scenario: Viewing variants
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see 2 variants
And I should see the "Iron Shield - very big" variant
And I should see the "Iron Shield - very small" variant
@ui @no-api
Scenario: Viewing variants' details
When I access the "Iron Shield" product
Then I should see 2 variants Then I should see 2 variants
And I should see "Iron Shield - very big" variant with code "123456789-xl", priced "$25.00" and current stock 5 and in "United States" channel And I should see "Iron Shield - very big" variant with code "123456789-xl", priced "$25.00" and current stock 5 and in "United States" channel
And I should see "Iron Shield - very small" variant with code "123456789-xs", priced "$15.00" and current stock 12 and in "United States" channel And I should see "Iron Shield - very small" variant with code "123456789-xs", priced "$15.00" and current stock 12 and in "United States" channel
@ui @javascript @api @ui @javascript @api
Scenario: Viewing media block Scenario: Viewing media
Given the "Iron Shield" product has an image "mugs.jpg" with "main" type Given the "Iron Shield" product has an image "mugs.jpg" with "main" type
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see an image related to this product Then I should see an image related to this product
@ui @ui @api
Scenario: Viewing "more details" block Scenario: Viewing more details
Given the product "Iron Shield" has the slug "iron-shield" Given the product "Iron Shield" has the slug "iron-shield"
And the description of product "Iron Shield" is "Shield created by dwarf" And the description of product "Iron Shield" is "Shield created by dwarf"
And the meta keywords of product "Iron Shield" is "shield" And the meta keywords of product "Iron Shield" is "shield"
And the short description of product "Iron Shield" is "good shield" And the short description of product "Iron Shield" is "good shield"
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see product name is "Iron Shield" Then I should see product name is "Iron Shield"
And I should see product slug is "iron-shield" And I should see product slug is "iron-shield"
And I should see product's description is "Shield created by dwarf" And I should see product's description is "Shield created by dwarf"
And I should see product's meta keywords is "shield" And I should see product's meta keywords is "shield"
And I should see product's short description is "good shield" And I should see product's short description is "good shield"
@ui @ui @api
Scenario: Viewing associations block Scenario: Viewing association types
Given the store has a "Glass shield" product Given the store has a "Glass shield" product
And the product "Iron Shield" has an association "Similar" with product "Glass shield" And the product "Iron Shield" has an association "Similar" with product "Glass shield"
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see product association type "Similar"
@ui @no-api
Scenario: Viewing associations
Given the store has a "Glass shield" product
And the product "Iron Shield" has an association "Similar" with product "Glass shield"
When I access the "Iron Shield" product
Then I should see product association "Similar" with "Glass shield" Then I should see product association "Similar" with "Glass shield"

View file

@ -11,54 +11,54 @@ Feature: Viewing details of a simple product
And I am logged in as an administrator And I am logged in as an administrator
And I am browsing products And I am browsing products
@ui @ui @no-api
Scenario: Viewing a simple product show page Scenario: Viewing a simple product show page
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see product show page without variants Then I should see product show page without variants
And I should see product name "Iron Shield" And I should see product name "Iron Shield"
And I should see product breadcrumb "Iron Shield" And I should see product breadcrumb "Iron Shield"
@ui @ui @no-api
Scenario: Viewing pricing block Scenario: Viewing pricing block
Given the product "Iron Shield" has original price "$25.00" Given the product "Iron Shield" has original price "$25.00"
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see price "$20.00" for channel "United States" Then I should see price "$20.00" for channel "United States"
And I should see original price "$25.00" for channel "United States" And I should see original price "$25.00" for channel "United States"
@ui @ui @no-api
Scenario: Viewing price block without channel enable Scenario: Viewing price block without channel enable
Given this product is unavailable in "United States" channel Given this product is unavailable in "United States" channel
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see product name "Iron Shield" Then I should see product name "Iron Shield"
And I should see the product in neither channel And I should see the product in neither channel
And I should not see price for channel "United States" And I should not see price for channel "United States"
@ui @ui @no-api
Scenario: Viewing details block Scenario: Viewing details block
Given the store has a tax category "No tax" with a code "nt" Given the store has a tax category "No tax" with a code "nt"
And product's "Iron Shield" code is "123456789" And product's "Iron Shield" code is "123456789"
And there are 4 units of product "Iron Shield" available in the inventory And there are 4 units of product "Iron Shield" available in the inventory
And the product "Iron Shield" belongs to "No tax" tax category And the product "Iron Shield" belongs to "No tax" tax category
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see product's code is "123456789" Then I should see product's code is "123456789"
And I should see the product is enabled for channel "United States" And I should see the product is enabled for channel "United States"
And I should see 4 as a current stock of this product And I should see 4 as a current stock of this product
And I should see product's tax category is "No tax" And I should see product's tax category is "No tax"
@ui @ui @no-api
Scenario: Viewing taxonomy block Scenario: Viewing taxonomy block
Given this product belongs to "Shield" Given this product belongs to "Shield"
And the product "Iron Shield" has a main taxon "Equipment" And the product "Iron Shield" has a main taxon "Equipment"
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see main taxon is "Equipment" Then I should see main taxon is "Equipment"
And I should see product taxon is "Shield" And I should see product taxon "Shield"
@ui @ui @no-api
Scenario: Viewing shipping block Scenario: Viewing shipping block
Given the store has "Over sized" and "Standard" shipping category Given the store has "Over sized" and "Standard" shipping category
And the product "Iron Shield" has height "10.0", width "15.0", depth "20.0", weight "25.0" And the product "Iron Shield" has height "10.0", width "15.0", depth "20.0", weight "25.0"
And this product belongs to "Over sized" shipping category And this product belongs to "Over sized" shipping category
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see product's shipping category is "Over sized" Then I should see product's shipping category is "Over sized"
And I should see product's height is 10 And I should see product's height is 10
And I should see product's width is 15 And I should see product's width is 15
@ -68,26 +68,26 @@ Feature: Viewing details of a simple product
@ui @javascript @api @ui @javascript @api
Scenario: Viewing media block Scenario: Viewing media block
Given the "Iron Shield" product has an image "mugs.jpg" with "main" type Given the "Iron Shield" product has an image "mugs.jpg" with "main" type
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see an image related to this product Then I should see an image related to this product
@ui @ui @no-api
Scenario: Viewing "more details" block Scenario: Viewing "more details" block
Given the product "Iron Shield" has the slug "iron-shield" Given the product "Iron Shield" has the slug "iron-shield"
And the description of product "Iron Shield" is "Shield created by dwarf" And the description of product "Iron Shield" is "Shield created by dwarf"
And the meta keywords of product "Iron Shield" is "shield" And the meta keywords of product "Iron Shield" is "shield"
And the short description of product "Iron Shield" is "good shield" And the short description of product "Iron Shield" is "good shield"
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see product name is "Iron Shield" Then I should see product name is "Iron Shield"
And I should see product slug is "iron-shield" And I should see product slug is "iron-shield"
And I should see product's description is "Shield created by dwarf" And I should see product's description is "Shield created by dwarf"
And I should see product's meta keywords is "shield" And I should see product's meta keywords is "shield"
And I should see product's short description is "good shield" And I should see product's short description is "good shield"
@ui @ui @no-api
Scenario: Viewing associations block Scenario: Viewing associations block
Given the store has "Similar" and "Dwarf equipment" product association types Given the store has "Similar" and "Dwarf equipment" product association types
And the store has a "Glass Shield" product And the store has a "Glass Shield" product
And the product "Iron Shield" has an association "Similar" with product "Glass Shield" And the product "Iron Shield" has an association "Similar" with product "Glass Shield"
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see product association "Similar" with "Glass Shield" Then I should see product association "Similar" with "Glass Shield"

View file

@ -1,4 +1,4 @@
@viewing_products @viewing_product_in_admin_panel
Feature: Seeing the lowest price before the discount for a simple product Feature: Seeing the lowest price before the discount for a simple product
In order to be aware of simple product prices In order to be aware of simple product prices
As an Administrator As an Administrator
@ -13,10 +13,10 @@ Feature: Seeing the lowest price before the discount for a simple product
@ui @no-api @ui @no-api
Scenario: Seeing price block with lowest price before the discount Scenario: Seeing price block with lowest price before the discount
Given this product's price changed to "$21.00" and original price changed to "$37.00" Given this product's price changed to "$21.00" and original price changed to "$37.00"
When I access "Bizon Z056" product page When I access the "Bizon Z056" product
Then I should see "$42.00" as its lowest price before the discount in "United States" channel Then I should see "$42.00" as its lowest price before the discount in "United States" channel
@ui @no-api @ui @no-api
Scenario: Seeing price block without lowest price before the discount Scenario: Seeing price block without lowest price before the discount
When I access "Bizon Z056" product page When I access the "Bizon Z056" product
Then I should not see the lowest price before the discount in "United States" channel Then I should not see the lowest price before the discount in "United States" channel

View file

@ -1,4 +1,4 @@
@viewing_products @viewing_product_in_admin_panel
Feature: Seeing the lowest price before the discount within variant Feature: Seeing the lowest price before the discount within variant
In order to be aware of variant's prices In order to be aware of variant's prices
As an Administrator As an Administrator
@ -15,6 +15,6 @@ Feature: Seeing the lowest price before the discount within variant
@ui @no-api @ui @no-api
Scenario: Seeing price block with lowest price before the discount within variant Scenario: Seeing price block with lowest price before the discount within variant
When I access "Wyborowa Vodka" product page When I access the "Wyborowa Vodka" product
Then I should not see the lowest price before the discount for "Wyborowa Vodka Rye" variant in "United States" channel Then I should not see the lowest price before the discount for "Wyborowa Vodka Rye" variant in "United States" channel
And I should see the lowest price before the discount of "$11.00" for "Wyborowa Vodka Potato" variant in "United States" channel And I should see the lowest price before the discount of "$11.00" for "Wyborowa Vodka Potato" variant in "United States" channel

View file

@ -1,4 +1,4 @@
@viewing_products @viewing_product_in_admin_panel
Feature: Viewing product's non translatable attributes Feature: Viewing product's non translatable attributes
In order to see product's non translatable attribute In order to see product's non translatable attribute
As an Administrator As an Administrator
@ -14,9 +14,9 @@ Feature: Viewing product's non translatable attributes
And I am logged in as an administrator And I am logged in as an administrator
And I am browsing products And I am browsing products
@ui @ui @api
Scenario: Viewing product's non translatable attributes along with default ones Scenario: Viewing product's non translatable attributes along with default ones
When I access "Iron Pickaxe" product page When I access the "Iron Pickaxe" product
Then I should see non-translatable attribute "crit chance" with value "10 %" Then I should see non-translatable attribute "crit chance" with value 10%
And I should see attribute "Material" with value "Iron" in "English (United States)" locale And I should see attribute "Material" with value "Iron" in "English (United States)" locale
And I should see attribute "Material" with value "Żelazo" in "Polish (Poland)" locale And I should see attribute "Material" with value "Żelazo" in "Polish (Poland)" locale

View file

@ -1,4 +1,4 @@
@viewing_products @viewing_product_in_admin_panel
Feature: Viewing product's attributes in different locales Feature: Viewing product's attributes in different locales
In order to see product's specification in all locales In order to see product's specification in all locales
As a Administrator As a Administrator
@ -15,9 +15,9 @@ Feature: Viewing product's attributes in different locales
And I am logged in as an administrator And I am logged in as an administrator
And I am browsing products And I am browsing products
@ui @ui @api
Scenario: Viewing product's attributes defined in different locales Scenario: Viewing product's attributes defined in different locales
When I access "Iron Shield" product page When I access the "Iron Shield" product
Then I should see attribute "material" with value "oak wood" in "English (United States)" locale Then I should see attribute "material" with value "oak wood" in "English (United States)" locale
And I should see attribute "shield details" with value "oak wood is a very good material." in "English (United States)" locale And I should see attribute "shield details" with value "oak wood is a very good material." in "English (United States)" locale
And I should see attribute "material" with value "drewno dębowe" in "Polish (Poland)" locale And I should see attribute "material" with value "drewno dębowe" in "Polish (Poland)" locale

View file

@ -14,7 +14,6 @@ Feature: Not seeing the lowest price for a product that has a taxon excluded on
And the store also has a product "Cauliflower" priced at "$25.00" And the store also has a product "Cauliflower" priced at "$25.00"
And it belongs to "Vegetables" and "Special offers" And it belongs to "Vegetables" and "Special offers"
And this product's price changed to "$15.00" and original price changed to "$25.00" And this product's price changed to "$15.00" and original price changed to "$25.00"
And I am logged in as an administrator
@api @ui @api @ui
Scenario: Not seeing the lowest price for a product that has a taxon excluded on the channel Scenario: Not seeing the lowest price for a product that has a taxon excluded on the channel

View file

@ -17,24 +17,24 @@ Feature: Viewing product's select attributes
Then I should see the product attribute "T-Shirt material" with value "Banana skin" on the list Then I should see the product attribute "T-Shirt material" with value "Banana skin" on the list
And I should also see the product attribute "T-Shirt material" with value "Cotton" on the list And I should also see the product attribute "T-Shirt material" with value "Cotton" on the list
@ui @ui @api
Scenario: Viewing a detailed page with product's select attribute after changing a value Scenario: Viewing a detailed page with product's select attribute after changing a value
Given this product has select attribute "T-Shirt material" with values "Banana skin" and "Cotton" Given this product has select attribute "T-Shirt material" with values "Banana skin" and "Cotton"
When the administrator changes this product attribute's value "Cotton" to "Orange skin" When this product attribute's value changed from "Cotton" to "Orange skin"
And I check this product's details And I check this product's details
Then I should see the product attribute "T-Shirt material" with value "Banana skin" on the list Then I should see the product attribute "T-Shirt material" with value "Banana skin" on the list
And I should also see the product attribute "T-Shirt material" with value "Orange skin" on the list And I should also see the product attribute "T-Shirt material" with value "Orange skin" on the list
@ui @javascript @ui @javascript @api @no-postgres
Scenario: Viewing a detailed page with product's select attribute after removing an only value Scenario: Viewing a detailed page with product's select attribute after removing an only value
Given this product has select attribute "T-Shirt material" with value "Cotton" Given this product has select attribute "T-Shirt material" with value "Cotton"
When the administrator deletes the value "Cotton" from this product attribute When this product attribute's value "Cotton" has been removed
And I check this product's details And I check this product's details
Then I should not see the product attribute "T-Shirt material" Then I should not see the product attribute "T-Shirt material"
@ui @javascript @ui @javascript @api @no-postgres
Scenario: Viewing a detailed page with product's select attribute after removing one of the value Scenario: Viewing a detailed page with product's select attribute after removing one of the value
Given this product has select attribute "T-Shirt material" with values "Banana skin" and "Cotton" Given this product has select attribute "T-Shirt material" with values "Banana skin" and "Cotton"
When the administrator deletes the value "Cotton" from this product attribute When this product attribute's value "Cotton" has been removed
And I check this product's details And I check this product's details
Then I should see the product attribute "T-Shirt material" with value "Banana skin" Then I should see the product attribute "T-Shirt material" with value "Banana skin"

View file

@ -1601,14 +1601,14 @@ parameters:
path: src/Sylius/Bundle/ApiBundle/EventSubscriber/TaxonDeletionEventSubscriber.php path: src/Sylius/Bundle/ApiBundle/EventSubscriber/TaxonDeletionEventSubscriber.php
- -
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\Filter\\\\Doctrine\\\\CatalogPromotionChannelFilter\\:\\:__construct\\(\\) has parameter \\$properties with no value type specified in iterable type array\\.$#" message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\Filter\\\\Doctrine\\\\ChannelsAwareChannelFilter\\:\\:__construct\\(\\) has parameter \\$properties with no value type specified in iterable type array\\.$#"
count: 1 count: 1
path: src/Sylius/Bundle/ApiBundle/Filter/Doctrine/CatalogPromotionChannelFilter.php path: src/Sylius/Bundle/ApiBundle/Filter/Doctrine/ChannelsAwareChannelFilter.php
- -
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\Filter\\\\Doctrine\\\\CatalogPromotionChannelFilter\\:\\:getDescription\\(\\) return type has no value type specified in iterable type array\\.$#" message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\Filter\\\\Doctrine\\\\ChannelsAwareChannelFilter\\:\\:getDescription\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1 count: 1
path: src/Sylius/Bundle/ApiBundle/Filter/Doctrine/CatalogPromotionChannelFilter.php path: src/Sylius/Bundle/ApiBundle/Filter/Doctrine/ChannelsAwareChannelFilter.php
- -
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\Filter\\\\Doctrine\\\\ExchangeRateFilter\\:\\:filterProperty\\(\\) has no return type specified\\.$#" message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\Filter\\\\Doctrine\\\\ExchangeRateFilter\\:\\:filterProperty\\(\\) has no return type specified\\.$#"

View file

@ -19,6 +19,9 @@ use Sylius\Behat\Client\ApiClientInterface;
use Sylius\Behat\Client\ResponseCheckerInterface; use Sylius\Behat\Client\ResponseCheckerInterface;
use Sylius\Behat\Context\Api\Resources; use Sylius\Behat\Context\Api\Resources;
use Sylius\Component\Core\Model\CatalogPromotionInterface; use Sylius\Component\Core\Model\CatalogPromotionInterface;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
final class BrowsingCatalogPromotionProductVariantsContext implements Context final class BrowsingCatalogPromotionProductVariantsContext implements Context
@ -40,6 +43,17 @@ final class BrowsingCatalogPromotionProductVariantsContext implements Context
$this->client->filter(); $this->client->filter();
} }
/**
* @When /^I want to view all variants of (this product)$/
* @When /^I view(?:| all) variants of the (product "[^"]+")$/
*/
public function iWantToViewAllVariantsOfThisProduct(ProductInterface $product): void
{
$this->client->index(Resources::PRODUCT_VARIANTS);
$this->client->addFilter('product', $this->iriConverter->getIriFromResource($product));
$this->client->filter();
}
/** /**
* @Then /^there should be (\d+) product variants? on the list$/ * @Then /^there should be (\d+) product variants? on the list$/
*/ */
@ -66,4 +80,71 @@ final class BrowsingCatalogPromotionProductVariantsContext implements Context
)); ));
} }
} }
/**
* @Then :variant variant price should be decreased by catalog promotion :catalogPromotion in :channel channel
*/
public function variantPriceShouldBeDecreasedByCatalogPromotion(
ProductVariantInterface $variant,
CatalogPromotionInterface $catalogPromotion,
ChannelInterface $channel,
): void {
Assert::true(
$this->variantHasCatalogPromotionInChannel($variant, $catalogPromotion, $channel),
sprintf(
'Catalog promotion "%s" was not found in applied promotions of variant "%s" in channel "%s".',
$catalogPromotion->getCode(),
$variant->getCode(),
$channel->getCode(),
),
);
}
/**
* @Then :variant variant price should not be decreased by catalog promotion :catalogPromotion in :channel channel
*/
public function variantPriceShouldNotBeDecreasedByCatalogPromotion(
ProductVariantInterface $variant,
CatalogPromotionInterface $catalogPromotion,
ChannelInterface $channel,
): void {
Assert::false(
$this->variantHasCatalogPromotionInChannel($variant, $catalogPromotion, $channel),
sprintf(
'Catalog promotion "%s" was found in applied promotions of variant "%s" in channel "%s".',
$catalogPromotion->getCode(),
$variant->getCode(),
$channel->getCode(),
),
);
}
private function variantHasCatalogPromotionInChannel(
ProductVariantInterface $variant,
CatalogPromotionInterface $catalogPromotion,
ChannelInterface $channel
): bool {
$variantData = $this->getDataOfVariantWithCode($variant->getCode());
$promotions = $variantData['channelPricings'][$channel->getCode()]['appliedPromotions'] ?? [];
foreach ($promotions as $promotion) {
if ($promotion['code'] === $catalogPromotion->getCode()) {
return true;
}
}
return false;
}
private function getDataOfVariantWithCode(string $code): array
{
$variantsData = $this->responseChecker->getCollection($this->client->getLastResponse());
foreach ($variantsData as $variantData) {
if ($variantData['code'] === $code) {
return $variantData;
}
}
throw new \InvalidArgumentException(sprintf('Variant with code "%s" was not found.', $code));
}
} }

View file

@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
namespace Sylius\Behat\Context\Api\Admin;
use ApiPlatform\Api\IriConverterInterface;
use Behat\Behat\Context\Context;
use Sylius\Behat\Client\ApiClientInterface;
use Sylius\Behat\Client\ResponseCheckerInterface;
use Sylius\Behat\Context\Api\Resources;
use Sylius\Behat\Service\Converter\SectionAwareIriConverterInterface;
use Sylius\Behat\Service\SharedStorageInterface;
use Sylius\Component\Core\Repository\ProductAssociationRepositoryInterface;
use Sylius\Component\Product\Model\ProductAssociationInterface;
use Sylius\Component\Product\Model\ProductAssociationTypeInterface;
use Sylius\Component\Product\Model\ProductInterface;
use Webmozart\Assert\Assert;
class ManagingProductAssociationsContext implements Context
{
public function __construct(
private ApiClientInterface $client,
private ResponseCheckerInterface $responseChecker,
private IriConverterInterface $iriConverter,
private SectionAwareIriConverterInterface $sectionAwareIriConverter,
private ProductAssociationRepositoryInterface $associationRepository,
private SharedStorageInterface $sharedStorage,
) {
}
/**
* @When /^I (associate as "[^"]+") the (product "[^"]+") with the ("[^"]+" product)$/
*/
public function iAssociateAsTypeTheProductWithTheProduct(
ProductAssociationTypeInterface $type,
ProductInterface $owner,
ProductInterface $product,
): void {
$this->iAssociateAsTypeTheProductWithTheProducts($type, $owner, [$product]);
}
/**
* @When /^I (associate as "[^"]+") the (product "[^"]+") with the (products "[^"]+" and "[^"]+")$/
*/
public function iAssociateAsTypeTheProductWithTheProducts(
ProductAssociationTypeInterface $type,
ProductInterface $owner,
array $products,
): void {
$associatedProductsData = [];
/** @var ProductInterface $product */
foreach ($products as $product) {
$associatedProductsData[] = $this->iriConverter->getIriFromItem($product);
}
$this->client->buildCreateRequest(Resources::PRODUCT_ASSOCIATIONS);
$this->client->addRequestData('type', $this->iriConverter->getIriFromItem($type));
$this->client->addRequestData('owner', $this->iriConverter->getIriFromItem($owner));
$this->client->addRequestData('associatedProducts', $associatedProductsData);
$this->client->create();
/** @var ProductAssociationInterface $association */
$association = $this->associationRepository->findOneBy([
'owner' => $owner,
'type' => $type,
]);
$this->sharedStorage->set('association', $association);
$this->sharedStorage->set('product', $association->getOwner());
}
/**
* @When /^I add the (product "[^"]+") to (this product association)$/
*/
public function iAddTheProductToThisProductAssociation(
ProductInterface $product,
ProductAssociationInterface $association,
): void {
$this->client->buildUpdateRequest(Resources::PRODUCT_ASSOCIATIONS, (string) $association->getId());
$associatedProducts = [$this->iriConverter->getIriFromItem($product)];
foreach ($association->getAssociatedProducts() as $associatedProduct) {
$associatedProducts[] = $this->iriConverter->getIriFromItem($associatedProduct);
}
$this->client->setRequestData(['associatedProducts' => $associatedProducts]);
$this->client->update();
$this->sharedStorage->set('association', $association);
}
/**
* @When /^I change (this product association)'s product to the ("[^"]+" product)$/
*/
public function iChangeThisProductAssociationProductToProduct(
ProductAssociationInterface $association,
ProductInterface $product,
): void {
$this->client->buildUpdateRequest(Resources::PRODUCT_ASSOCIATIONS, (string) $association->getId());
$this->client->addRequestData('associatedProducts', [$this->iriConverter->getIriFromItem($product)]);
$this->client->update();
$this->sharedStorage->set('association', $association);
}
/**
* @When /^I remove the (product "[^"]+") from (this product association)$/
*/
public function iRemoveTheProductFromThisProductAssociation(
ProductInterface $product,
ProductAssociationInterface $association,
): void {
$this->client->buildUpdateRequest(Resources::PRODUCT_ASSOCIATIONS, (string) $association->getId());
$associatedProducts = [];
foreach ($association->getAssociatedProducts() as $associatedProduct) {
if ($associatedProduct->getCode() !== $product->getCode()) {
$associatedProducts[] = $this->iriConverter->getIriFromItem($associatedProduct);
}
}
$this->client->setRequestData(['associatedProducts' => $associatedProducts]);
$this->client->update();
$this->sharedStorage->set('association', $association);
}
/**
* @Then /^(this product) should have an (association "[^"]+")$/
*/
public function thisProductShouldHaveAnAssociation(
ProductInterface $product,
ProductAssociationTypeInterface $type,
): void {
$response = $this->client->show(Resources::PRODUCTS, $product->getCode());
$associations = $this->responseChecker->getValue($response, 'associations');
$associationTypeIri = $this->sectionAwareIriConverter->getIriFromResourceInSection($type, 'admin');
foreach ($associations as $associationIri) {
$response = $this->client->showByIri($associationIri);
$productAssociationType = $this->responseChecker->getValue($response, 'type');
if ($associationTypeIri === $productAssociationType) {
return;
}
}
throw new \InvalidArgumentException(sprintf(
'Product %s does not have an association of type %s',
$product->getCode(),
$type->getName()
));
}
/**
* @Then /^(this association) should only have (product "[^"]+")$/
*/
public function thisAssociationShouldOnlyHaveProduct(
ProductAssociationInterface $association,
ProductInterface $product,
): void {
$this->thisAssociationShouldHaveProducts($association, [$product]);
}
/**
* @Then /^(this association) should have (products "[^"]+" and "[^"]+")$/
*/
public function thisAssociationShouldHaveProducts(
ProductAssociationInterface $association,
array $products,
): void {
$response = $this->client->show(Resources::PRODUCT_ASSOCIATIONS, (string) $association->getId());
$content = $this->responseChecker->getResponseContent($response);
$associatedProducts = $content['associatedProducts'];
Assert::count($associatedProducts, count($products));
/** @var ProductInterface $product */
foreach ($products as $product) {
Assert::inArray(
$this->sectionAwareIriConverter->getIriFromResourceInSection($product, 'admin'),
$associatedProducts,
);
}
}
}

View file

@ -16,7 +16,9 @@ namespace Sylius\Behat\Context\Api\Admin;
use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\IriConverterInterface;
use Behat\Behat\Context\Context; use Behat\Behat\Context\Context;
use Sylius\Behat\Client\ApiClientInterface; use Sylius\Behat\Client\ApiClientInterface;
use Sylius\Behat\Client\ResponseCheckerInterface;
use Sylius\Behat\Context\Api\Resources; use Sylius\Behat\Context\Api\Resources;
use Sylius\Behat\Service\SharedStorageInterface;
use Sylius\Component\Core\Model\ProductInterface; use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductTaxonInterface; use Sylius\Component\Core\Model\ProductTaxonInterface;
use Sylius\Component\Core\Model\TaxonInterface; use Sylius\Component\Core\Model\TaxonInterface;
@ -24,8 +26,47 @@ use Webmozart\Assert\Assert;
final class ManagingProductTaxonsContext implements Context final class ManagingProductTaxonsContext implements Context
{ {
public function __construct(private ApiClientInterface $client, private IriConverterInterface $iriConverter) public function __construct(
private ApiClientInterface $client,
private IriConverterInterface $iriConverter,
private ResponseCheckerInterface $responseChecker,
private SharedStorageInterface $sharedStorage,
) {
}
/**
* @When /^I am browsing the (\d+)(?:st|nd|rd|th) page of products from ("([^"]+)" taxon)$/
* @When /^I go to the (\d+)(?:st|nd|rd|th) page of products from ("([^"]+)" taxon)$/
*/
public function iAmBrowsingThePageOfProductsFromTaxon(int $page, TaxonInterface $taxon): void
{ {
$this->iAmBrowsingProductsFromTaxon($taxon);
$this->client->addFilter('page', $page);
$this->client->filter();
$this->sharedStorage->set('response', $this->client->getLastResponse());
}
/**
* @When /^I am browsing products from ("([^"]+)" taxon)$/
*/
public function iAmBrowsingProductsFromTaxon(TaxonInterface $taxon): void
{
$this->client->index(Resources::PRODUCT_TAXONS);
$this->client->addFilter('taxon.code', $taxon->getCode());
$this->client->addFilter('itemsPerPage', 10);
$this->client->filter();
$this->sharedStorage->set('response', $this->client->getLastResponse());
}
/**
* @When I set the position of :product to :position
*/
public function iSetThePositionOfProductTo(ProductInterface $product, string|int $position): void
{
$this->client->buildUpdateRequest(Resources::PRODUCT_TAXONS, (string) $product->getProductTaxons()->current()->getId());
$this->client->updateRequestData(['position' => is_numeric($position) ? (int) $position : $position]);
} }
/** /**
@ -84,6 +125,38 @@ final class ManagingProductTaxonsContext implements Context
$this->client->delete(Resources::PRODUCT_TAXONS, (string) $productTaxon->getId()); $this->client->delete(Resources::PRODUCT_TAXONS, (string) $productTaxon->getId());
} }
/**
* @When I sort this taxon's products :order by :field
*/
public function iSortProductsBy(string $order, string $field): void
{
$this->client->sort([$field => ManagingProductsContext::SORT_TYPES[$order]]);
$this->sharedStorage->set('response', $this->client->getLastResponse());
}
/**
* @When I save my new configuration
*/
public function iSaveMyNewConfiguration(): void
{
$this->client->update();
$this->sharedStorage->set('response', $this->client->getLastResponse());
}
/**
* @Then /^the (first|last) product on the list within this taxon should have name "([^"]+)"$/
*/
public function theLastProductOnTheListWithinThisTaxonShouldHaveName(string $position, string $name): void
{
$productTaxons = $this->responseChecker->getCollection($this->sharedStorage->get('response'));
$productTaxon = $position === 'last' ? end($productTaxons) : reset($productTaxons);
/** @var ProductInterface $product */
$product = $this->iriConverter->getResourceFromIri($productTaxon['product']);
Assert::same($product->getTranslation()->getName(), $name);
}
/** /**
* @Then I should be notified that specifying a :part is required * @Then I should be notified that specifying a :part is required
*/ */
@ -105,4 +178,15 @@ final class ManagingProductTaxonsContext implements Context
'Product taxons cannot be duplicated.', 'Product taxons cannot be duplicated.',
); );
} }
/**
* @Then I should be notified that the position :position is invalid
*/
public function iShouldBeNotifiedThatThePositionIsInvalid(): void
{
Assert::contains(
(string) $this->responseChecker->getError($this->client->getLastResponse()),
'The type of the "position" attribute must be "int", "string" given.',
);
}
} }

View file

@ -24,7 +24,12 @@ use Sylius\Component\Attribute\Model\AttributeValueInterface;
use Sylius\Component\Core\Model\AdminUserInterface; use Sylius\Component\Core\Model\AdminUserInterface;
use Sylius\Component\Core\Model\ChannelInterface; use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\ProductInterface; use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductTaxonInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\Model\TaxonInterface; use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Locale\Model\LocaleInterface;
use Sylius\Component\Product\Model\ProductAssociationInterface;
use Sylius\Component\Product\Model\ProductAssociationTypeInterface;
use Sylius\Component\Product\Model\ProductAttributeInterface; use Sylius\Component\Product\Model\ProductAttributeInterface;
use Sylius\Component\Product\Model\ProductOptionInterface; use Sylius\Component\Product\Model\ProductOptionInterface;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@ -396,13 +401,21 @@ final class ManagingProductsContext implements Context
} }
/** /**
* @When I access :product product page * @When I access the :product product
*/ */
public function iAccessProductPage(ProductInterface $product): void public function iAccessTheProduct(ProductInterface $product): void
{ {
$this->client->show(Resources::PRODUCTS, $product->getCode()); $this->client->show(Resources::PRODUCTS, $product->getCode());
} }
/**
* @When I choose :channel as a channel filter
*/
public function iChooseChannelAsAChannelFilter(ChannelInterface $channel): void
{
$this->client->addFilter('channel', $this->iriConverter->getIriFromResource($channel));
}
/** /**
* @When I save my changes to the images * @When I save my changes to the images
*/ */
@ -411,6 +424,125 @@ final class ManagingProductsContext implements Context
// Intentionally left blank // Intentionally left blank
} }
/**
* @When I filter
*/
public function iFilter(): void
{
$this->client->filter();
$this->sharedStorage->set('response', $this->client->getLastResponse());
}
/**
* @Then I should see main taxon is :taxon
*/
public function iShouldSeeMainTaxonIs(TaxonInterface $taxon): void
{
Assert::same(
$this->responseChecker->getValue($this->client->getLastResponse(), 'mainTaxon'),
$this->sectionAwareIriConverter->getIriFromResourceInSection($taxon, 'admin'),
);
}
/**
* @Then I should see product taxon :taxon
*/
public function iShouldSeeProductTaxon(TaxonInterface $taxon): void
{
$product = $this->sharedStorage->get('product');
Assert::isInstanceOf($product, ProductInterface::class);
$productTaxon = $product->getProductTaxons()->filter(
fn(ProductTaxonInterface $productTaxon) => $productTaxon->getTaxon()->getCode() === $taxon->getCode()
)->first();
Assert::isInstanceOf($productTaxon, ProductTaxonInterface::class);
Assert::true($this->responseChecker->hasValueInCollection(
$this->client->getLastResponse(),
'productTaxons',
$this->sectionAwareIriConverter->getIriFromResourceInSection($productTaxon, 'admin'),
));
}
/**
* @Then I should see option :productOption
*/
public function iShouldSeeOption(ProductOptionInterface $productOption): void
{
Assert::true($this->responseChecker->hasValueInCollection(
$this->client->getLastResponse(),
'options',
$this->sectionAwareIriConverter->getIriFromResourceInSection($productOption, 'admin'),
));
}
/**
* @Then I should see :count variants
*/
public function iShouldSeeVariants(int $count): void
{
Assert::count(
$this->responseChecker->getResponseContent($this->client->getLastResponse())['variants'] ?? [],
$count,
);
}
/**
* @Then I should see the :variant variant
*/
public function iShouldSeeTheVariant(ProductVariantInterface $variant): void
{
Assert::true($this->responseChecker->hasValueInCollection(
$this->client->getLastResponse(),
'variants',
$this->sectionAwareIriConverter->getIriFromResourceInSection($variant, 'admin'),
));
}
/**
* @Then I should see product :field is :value
* @Then I should see product's :field is :value
*/
public function iShouldSeeProductFieldIs(string $field, string $value): void
{
$this->assertResponseHasTranslationFieldWithValue($field, $value);
}
/**
* @Then I should see product's meta keyword(s) is/are :metaKeywords
*/
public function iShouldSeeProductMetaKeywordsAre(string $metaKeywords): void
{
$this->assertResponseHasTranslationFieldWithValue('metaKeywords', $metaKeywords);
}
/**
* @Then I should see product's short description is :shortDescription
*/
public function iShouldSeeProductShortDescriptionIs(string $shortDescription): void
{
$this->assertResponseHasTranslationFieldWithValue('shortDescription', $shortDescription);
}
/**
* @Then I should see product association type :productAssociationType
*/
public function iShouldSeeProductAssociationType(ProductAssociationTypeInterface $productAssociationType): void
{
$associations = $this->responseChecker->getValue($this->client->getLastResponse(), 'associations');
foreach ($associations as $associationIri) {
/** @var ProductAssociationInterface $association */
$association = $this->iriConverter->getResourceFromIri($associationIri);
if ($association->getType()->getCode() === $productAssociationType->getCode()) {
return;
}
}
throw new \InvalidArgumentException(
sprintf('Product association type "%s" not found.', $productAssociationType->getCode()),
);
}
/** /**
* @Then I should be notified that it has been successfully created * @Then I should be notified that it has been successfully created
*/ */
@ -486,9 +618,10 @@ final class ManagingProductsContext implements Context
} }
/** /**
* @Then I should see a single product in the list
* @Then I should see :count products in the list * @Then I should see :count products in the list
*/ */
public function iShouldSeeProductsInTheList(int $count): void public function iShouldSeeProductsInTheList(int $count = 1): void
{ {
Assert::count($this->responseChecker->getCollection($this->client->getLastResponse()), $count); Assert::count($this->responseChecker->getCollection($this->client->getLastResponse()), $count);
} }
@ -612,11 +745,31 @@ final class ManagingProductsContext implements Context
*/ */
public function theFirstProductOnTheListShouldHave(string $field, string $value): void public function theFirstProductOnTheListShouldHave(string $field, string $value): void
{ {
$response = $this->getLastResponse(); $products = $this->responseChecker->getCollection($this->getLastResponse());
$products = $this->responseChecker->getCollection($response); Assert::same($this->getFieldValueOfProduct($products[0], $field), $value);
}
Assert::same($this->getFieldValueOfFirstProduct($products[0], $field), $value); /**
* @Then the last product on the list should have name :name
*/
public function theLastProductOnTheListShouldHaveName(string $name): void
{
$products = $this->responseChecker->getCollection($this->getLastResponse());
Assert::same($this->getFieldValueOfProduct(end($products), 'name'), $name);
}
/**
* @Then /^the (first|last) product on the list shouldn't have a name$/
*/
public function theProductOnTheListShouldNotHaveAName(string $position): void
{
$products = $this->responseChecker->getCollection($this->getLastResponse());
$product = $position === 'last' ? end($products) : reset($products);
Assert::null($this->getFieldValueOfProduct($product, 'name'));
} }
/** /**
@ -820,6 +973,17 @@ final class ManagingProductsContext implements Context
Assert::notEmpty($this->responseChecker->getValue($this->client->getLastResponse(), 'images')); Assert::notEmpty($this->responseChecker->getValue($this->client->getLastResponse(), 'images'));
} }
/**
* @Then I should see attribute :attribute with value :value in :locale locale
*/
public function iShouldSeeAttributeWithValueInLocale(
ProductAttributeInterface $attribute,
string $value,
LocaleInterface $locale,
): void {
$this->hasAttributeWithValueInLastResponse($attribute, $value, $locale->getCode());
}
private function getAdminLocaleCode(): string private function getAdminLocaleCode(): string
{ {
/** @var AdminUserInterface $adminUser */ /** @var AdminUserInterface $adminUser */
@ -830,14 +994,14 @@ final class ManagingProductsContext implements Context
return $this->responseChecker->getValue($response, 'localeCode'); return $this->responseChecker->getValue($response, 'localeCode');
} }
private function getFieldValueOfFirstProduct(array $product, string $field): ?string private function getFieldValueOfProduct(array $product, string $field): ?string
{ {
if ($field === 'code') { if ($field === 'code') {
return $product['code']; return $product['code'];
} }
if ($field === 'name') { if ($field === 'name') {
return $product['translations'][$this->getAdminLocaleCode()]['name']; return $product['translations'][$this->getAdminLocaleCode()]['name'] ?? null;
} }
return null; return null;
@ -934,4 +1098,12 @@ final class ManagingProductsContext implements Context
Assert::same((string) $value, $expectedValue); Assert::same((string) $value, $expectedValue);
} }
private function assertResponseHasTranslationFieldWithValue(string $field, string $value): void
{
Assert::same(
$this->responseChecker->getTranslationValue($this->client->getLastResponse(), $field),
$value,
);
}
} }

View file

@ -57,6 +57,8 @@ final class Resources
public const PRODUCT_ASSOCIATION_TYPES = 'product-association-types'; public const PRODUCT_ASSOCIATION_TYPES = 'product-association-types';
public const PRODUCT_ASSOCIATIONS = 'product-associations';
public const PRODUCT_ATTRIBUTES = 'product-attributes'; public const PRODUCT_ATTRIBUTES = 'product-attributes';
public const PRODUCT_IMAGES = 'product-images'; public const PRODUCT_IMAGES = 'product-images';

View file

@ -37,8 +37,15 @@ final class ProductAttributeContext implements Context
public function iShouldSeeTheProductAttributeWithValue(string $attributeName, string $expectedAttribute): void public function iShouldSeeTheProductAttributeWithValue(string $attributeName, string $expectedAttribute): void
{ {
$attribute = $this->getAttributeByName($attributeName); $attribute = $this->getAttributeByName($attributeName);
$attributeValue = $attribute['value'];
Assert::same($attribute['value'], $expectedAttribute); if (is_array($attributeValue)) {
Assert::inArray($expectedAttribute, $attributeValue);
return;
}
Assert::same($attributeValue, $expectedAttribute);
} }
/** /**
@ -71,6 +78,14 @@ final class ProductAttributeContext implements Context
Assert::inArray($expectedAttribute, $attribute['value']); Assert::inArray($expectedAttribute, $attribute['value']);
} }
/**
* @Then I should not see the product attribute :attributeName
*/
public function iShouldNotSeeTheProductAttribute(string $attributeName): void
{
Assert::false($this->hasAttributeByName($attributeName));
}
/** /**
* @Then I should (also) see the product attribute :attributeName with date :expectedAttribute * @Then I should (also) see the product attribute :attributeName with date :expectedAttribute
*/ */
@ -113,6 +128,17 @@ final class ProductAttributeContext implements Context
Assert::same($attribute['name'], $name); Assert::same($attribute['name'], $name);
} }
private function hasAttributeByName(string $name): bool
{
foreach ($this->getAttributes() as $attribute) {
if ($attribute['name'] === $name) {
return true;
}
}
return false;
}
private function getAttributeByName(string $name): array private function getAttributeByName(string $name): array
{ {
foreach ($this->getAttributes() as $attribute) { foreach ($this->getAttributes() as $attribute) {

View file

@ -140,6 +140,8 @@ final class ProductAssociationContext implements Context
$product->addAssociation($productAssociation); $product->addAssociation($productAssociation);
$this->productAssociationRepository->add($productAssociation); $this->productAssociationRepository->add($productAssociation);
$this->sharedStorage->set('product_association', $productAssociation);
} }
private function addProductAssociationTypeTranslation( private function addProductAssociationTypeTranslation(

View file

@ -423,6 +423,59 @@ final class ProductAttributeContext implements Context
$this->objectManager->flush(); $this->objectManager->flush();
} }
/**
* @When /^(this product attribute)'s value changed from "([^"]+)" to "([^"]+)"$/
*/
public function thisAttributeValueChangedFromTo(
ProductAttributeInterface $attribute,
string $from,
string $to,
): void {
$configuration = $attribute->getConfiguration();
$choices = $configuration['choices'] ?? [];
foreach ($choices as $uuid => $choice) {
foreach ($choice as $localeCode => $item) {
if ($item === $from) {
$choices[$uuid][$localeCode] = $to;
break 2;
}
}
}
$configuration['choices'] = $choices;
$attribute->setConfiguration($configuration);
$this->objectManager->flush();
}
/**
* @When /^(this product attribute)'s value "([^"]+)" has been removed$/
*/
public function thisAttributeValueHasBeenRemoved(
ProductAttributeInterface $attribute,
string $value,
): void {
$configuration = $attribute->getConfiguration();
$choices = $configuration['choices'] ?? [];
foreach ($choices as $uuid => $choice) {
foreach ($choice as $item) {
if ($value === $item) {
unset($choices[$uuid]);
break 2;
}
}
}
$configuration['choices'] = $choices;
$attribute->setConfiguration($configuration);
$this->objectManager->flush();
}
private function createProductAttribute( private function createProductAttribute(
string $type, string $type,
string $name, string $name,

View file

@ -25,6 +25,7 @@ final class ProductAssociationTypeContext implements Context
/** /**
* @Transform /^association "([^"]+)"$/ * @Transform /^association "([^"]+)"$/
* @Transform /^associate as "([^"]+)"$/
* @Transform :productAssociationType * @Transform :productAssociationType
*/ */
public function getProductAssociationTypeByName($productAssociationTypeName) public function getProductAssociationTypeByName($productAssociationTypeName)

View file

@ -18,8 +18,6 @@ use Sylius\Behat\Page\Admin\Crud\IndexPageInterface;
use Sylius\Behat\Page\Admin\ProductAttribute\CreatePageInterface; use Sylius\Behat\Page\Admin\ProductAttribute\CreatePageInterface;
use Sylius\Behat\Page\Admin\ProductAttribute\UpdatePageInterface; use Sylius\Behat\Page\Admin\ProductAttribute\UpdatePageInterface;
use Sylius\Behat\Service\Resolver\CurrentPageResolverInterface; use Sylius\Behat\Service\Resolver\CurrentPageResolverInterface;
use Sylius\Behat\Service\SharedSecurityServiceInterface;
use Sylius\Component\Core\Model\AdminUserInterface;
use Sylius\Component\Product\Model\ProductAttributeInterface; use Sylius\Component\Product\Model\ProductAttributeInterface;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
@ -30,7 +28,6 @@ final class ManagingProductAttributesContext implements Context
private IndexPageInterface $indexPage, private IndexPageInterface $indexPage,
private UpdatePageInterface $updatePage, private UpdatePageInterface $updatePage,
private CurrentPageResolverInterface $currentPageResolver, private CurrentPageResolverInterface $currentPageResolver,
private SharedSecurityServiceInterface $sharedSecurityService,
) { ) {
} }
@ -233,25 +230,6 @@ final class ManagingProductAttributesContext implements Context
$this->indexPage->open(); $this->indexPage->open();
} }
/**
* @When /^(the administrator) changes (this product attribute)'s value "([^"]*)" to "([^"]*)"$/
*/
public function theAdministratorChangesThisProductAttributesValueTo(
AdminUserInterface $user,
ProductAttributeInterface $productAttribute,
string $oldValue,
string $newValue,
): void {
$this->sharedSecurityService->performActionAsAdminUser(
$user,
function () use ($productAttribute, $oldValue, $newValue) {
$this->iWantToEditThisAttribute($productAttribute);
$this->iChangeItsValueTo($oldValue, $newValue);
$this->iSaveMyChanges();
},
);
}
/** /**
* @When I specify its min length as :min * @When I specify its min length as :min
* @When I specify its min entries value as :min * @When I specify its min entries value as :min
@ -286,24 +264,6 @@ final class ManagingProductAttributesContext implements Context
// Intentionally left blank to fulfill context expectation // Intentionally left blank to fulfill context expectation
} }
/**
* @When /^(the administrator) deletes the value "([^"]+)" from (this product attribute)$/
*/
public function theAdministratorDeletesTheValueFromThisProductAttribute(
AdminUserInterface $user,
string $value,
ProductAttributeInterface $productAttribute,
): void {
$this->sharedSecurityService->performActionAsAdminUser(
$user,
function () use ($productAttribute, $value) {
$this->iWantToEditThisAttribute($productAttribute);
$this->iDeleteValue($value);
$this->iSaveMyChanges();
},
);
}
/** /**
* @When I check (also) the :productAttributeName product attribute * @When I check (also) the :productAttributeName product attribute
*/ */

View file

@ -310,6 +310,7 @@ final class ManagingProductsContext implements Context
/** /**
* @Then the first product on the list should have :field :value * @Then the first product on the list should have :field :value
* @Then the first product on the list within this taxon should have :field :value
*/ */
public function theFirstProductOnTheListShouldHave($field, $value) public function theFirstProductOnTheListShouldHave($field, $value)
{ {
@ -366,6 +367,7 @@ final class ManagingProductsContext implements Context
/** /**
* @Then the last product on the list should have :field :value * @Then the last product on the list should have :field :value
* @Then the last product on the list within this taxon should have :field :value
*/ */
public function theLastProductOnTheListShouldHave($field, $value) public function theLastProductOnTheListShouldHave($field, $value)
{ {
@ -400,6 +402,17 @@ final class ManagingProductsContext implements Context
$this->indexPage->sortBy($field); $this->indexPage->sortBy($field);
} }
/**
* @When I sort this taxon's products :sortType by :field
*/
public function iSortThisTaxonsProductsBy(string $sortType, string $field): void
{
$this->indexPerTaxonPage->sortBy(
$field,
str_starts_with($sortType, 'de') ? 'desc' : 'asc',
);
}
/** /**
* @Then I should see a single product in the list * @Then I should see a single product in the list
* @Then I should see :numberOfProducts products in the list * @Then I should see :numberOfProducts products in the list

View file

@ -60,9 +60,9 @@ final class ProductShowPageContext implements Context
} }
/** /**
* @When I access :product product page * @When I access the :product product
*/ */
public function iAccessProductPage(ProductInterface $product): void public function iAccessTheProduct(ProductInterface $product): void
{ {
$this->indexPage->showProductPage($product->getName()); $this->indexPage->showProductPage($product->getName());
} }
@ -285,9 +285,9 @@ final class ProductShowPageContext implements Context
} }
/** /**
* @Then I should see product taxon is :taxonName * @Then I should see product taxon :taxonName
*/ */
public function iShouldSeeProductTaxonIs(string $taxonName): void public function iShouldSeeProductTaxon(string $taxonName): void
{ {
Assert::true($this->taxonomyElement->hasProductTaxon($taxonName)); Assert::true($this->taxonomyElement->hasProductTaxon($taxonName));
} }
@ -385,7 +385,15 @@ final class ProductShowPageContext implements Context
*/ */
public function iShouldSeeProductAssociationWith(string $association, string $productName): void public function iShouldSeeProductAssociationWith(string $association, string $productName): void
{ {
Assert::true($this->associationsElement->isProductAssociated($association, $productName)); Assert::true($this->associationsElement->isAssociatedWith($association, $productName));
}
/**
* @Then I should see product association type :association
*/
public function iShouldSeeProductAssociationType(string $association): void
{
Assert::true($this->associationsElement->hasAssociation($association));
} }
/** /**
@ -423,6 +431,14 @@ final class ProductShowPageContext implements Context
)); ));
} }
/**
* @Then I should see the :variantName variant
*/
public function iShouldSeeTheVariant(string $variantName): void
{
Assert::true($this->variantsElement->hasProductVariant($variantName));
}
/** /**
* @Then I should see attribute :attribute with value :value in :nameOfLocale locale * @Then I should see attribute :attribute with value :value in :nameOfLocale locale
*/ */
@ -432,7 +448,7 @@ final class ProductShowPageContext implements Context
} }
/** /**
* @Then I should see non-translatable attribute :attribute with value :value * @Then /^I should see non-translatable attribute "([^"]+)" with value ([^"]+)%$/
*/ */
public function iShouldSeeNonTranslatableAttributeWithValue(string $attribute, string $value): void public function iShouldSeeNonTranslatableAttributeWithValue(string $attribute, string $value): void
{ {

View file

@ -18,9 +18,13 @@ use FriendsOfBehat\PageObjectExtension\Element\Element;
final class AssociationsElement extends Element implements AssociationsElementInterface final class AssociationsElement extends Element implements AssociationsElementInterface
{ {
public function isProductAssociated(string $associationName, string $productName): bool public function hasAssociation(string $associationName): bool
{
return [] !== $this->getAssociatedProducts($this->getElement('associations'), $associationName);
}
public function isAssociatedWith(string $associationName, string $productName): bool
{ {
/** @var NodeElement $associations */
$associations = $this->getElement('associations'); $associations = $this->getElement('associations');
/** @var NodeElement $product */ /** @var NodeElement $product */

View file

@ -15,5 +15,7 @@ namespace Sylius\Behat\Element\Product\ShowPage;
interface AssociationsElementInterface interface AssociationsElementInterface
{ {
public function isProductAssociated(string $associationName, string $productName): bool; public function hasAssociation(string $associationName): bool;
public function isAssociatedWith(string $associationName, string $productName): bool;
} }

View file

@ -30,7 +30,7 @@ final class AttributesElement extends Element implements AttributesElementInterf
{ {
$attributeValue = $this->getDocument()->find('css', sprintf('.ui.segment[data-tab="non-translatable"] tr:contains("%s") td:nth-child(2)', $attribute))->getText(); $attributeValue = $this->getDocument()->find('css', sprintf('.ui.segment[data-tab="non-translatable"] tr:contains("%s") td:nth-child(2)', $attribute))->getText();
return $attributeValue === $value; return str_contains($attributeValue, $value);
} }
protected function getDefinedElements(): array protected function getDefinedElements(): array

View file

@ -26,6 +26,20 @@ final class VariantsElement extends Element implements VariantsElementInterface
return \count($variants); return \count($variants);
} }
public function hasProductVariant(string $name): bool
{
$variantRows = $this->getDocument()->findAll('css', '#variants .variants-accordion__title');
/** @var NodeElement $variant */
foreach ($variantRows as $variant) {
if ($variant->find('css', '.content .variant-name')->getText() === $name) {
return true;
}
}
return false;
}
public function hasProductVariantWithCodePriceAndCurrentStock( public function hasProductVariantWithCodePriceAndCurrentStock(
string $name, string $name,
string $code, string $code,

View file

@ -17,6 +17,8 @@ interface VariantsElementInterface
{ {
public function countVariantsOnPage(): int; public function countVariantsOnPage(): int;
public function hasProductVariant(string $name): bool;
public function hasProductVariantWithCodePriceAndCurrentStock( public function hasProductVariantWithCodePriceAndCurrentStock(
string $name, string $name,
string $code, string $code,

View file

@ -62,6 +62,8 @@
<service id="Sylius\Behat\Context\Api\Admin\ManagingProductTaxonsContext"> <service id="Sylius\Behat\Context\Api\Admin\ManagingProductTaxonsContext">
<argument type="service" id="sylius.behat.api_platform_client.admin" /> <argument type="service" id="sylius.behat.api_platform_client.admin" />
<argument type="service" id="api_platform.iri_converter" /> <argument type="service" id="api_platform.iri_converter" />
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
<argument type="service" id="sylius.behat.shared_storage" />
</service> </service>
<service id="sylius.behat.context.api.admin.managing_channels" class="Sylius\Behat\Context\Api\Admin\ManagingChannelsContext"> <service id="sylius.behat.context.api.admin.managing_channels" class="Sylius\Behat\Context\Api\Admin\ManagingChannelsContext">
@ -101,6 +103,15 @@
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" /> <argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
</service> </service>
<service id="sylius.behat.context.api.admin.managing_product_associations" class="Sylius\Behat\Context\Api\Admin\ManagingProductAssociationsContext">
<argument type="service" id="sylius.behat.api_platform_client.admin" />
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
<argument type="service" id="api_platform.iri_converter" />
<argument type="service" id="sylius.behat.section_iri_converter" />
<argument type="service" id="sylius.repository.product_association" />
<argument type="service" id="sylius.behat.shared_storage" />
</service>
<service id="sylius.behat.context.api.admin.managing_product_association_types" class="Sylius\Behat\Context\Api\Admin\ManagingProductAssociationTypesContext"> <service id="sylius.behat.context.api.admin.managing_product_association_types" class="Sylius\Behat\Context\Api\Admin\ManagingProductAssociationTypesContext">
<argument type="service" id="sylius.behat.api_platform_client.admin" /> <argument type="service" id="sylius.behat.api_platform_client.admin" />
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" /> <argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />

View file

@ -207,7 +207,6 @@
<argument type="service" id="sylius.behat.page.admin.product_attribute.index" /> <argument type="service" id="sylius.behat.page.admin.product_attribute.index" />
<argument type="service" id="sylius.behat.page.admin.product_attribute.update" /> <argument type="service" id="sylius.behat.page.admin.product_attribute.update" />
<argument type="service" id="sylius.behat.current_page_resolver" /> <argument type="service" id="sylius.behat.current_page_resolver" />
<argument type="service" id="sylius.behat.shared_security" />
</service> </service>
<service id="sylius.behat.context.ui.admin.managing_product_options" class="Sylius\Behat\Context\Ui\Admin\ManagingProductOptionsContext"> <service id="sylius.behat.context.ui.admin.managing_product_options" class="Sylius\Behat\Context\Ui\Admin\ManagingProductOptionsContext">

View file

@ -44,6 +44,7 @@ default:
- sylius.behat.context.setup.taxonomy - sylius.behat.context.setup.taxonomy
- sylius.behat.context.setup.zone - sylius.behat.context.setup.zone
- sylius.behat.context.api.admin.managing_product_associations
- sylius.behat.context.api.admin.managing_product_images - sylius.behat.context.api.admin.managing_product_images
- sylius.behat.context.api.admin.managing_products - sylius.behat.context.api.admin.managing_products
- sylius.behat.context.api.admin.response - sylius.behat.context.api.admin.response

View file

@ -8,20 +8,38 @@ default:
- sylius.behat.context.hook.doctrine_orm - sylius.behat.context.hook.doctrine_orm
- sylius.behat.context.transform.channel - sylius.behat.context.transform.channel
- sylius.behat.context.transform.currency
- sylius.behat.context.transform.lexical - sylius.behat.context.transform.lexical
- sylius.behat.context.transform.locale
- sylius.behat.context.transform.product - sylius.behat.context.transform.product
- sylius.behat.context.transform.product_association_type
- sylius.behat.context.transform.product_attribute
- sylius.behat.context.transform.product_option - sylius.behat.context.transform.product_option
- sylius.behat.context.transform.product_option_value
- sylius.behat.context.transform.product_variant
- sylius.behat.context.transform.shared_storage
- sylius.behat.context.transform.shipping_category
- sylius.behat.context.transform.tax_category
- sylius.behat.context.transform.taxon - sylius.behat.context.transform.taxon
- Sylius\Behat\Context\Transform\CatalogPromotionContext
- sylius.behat.context.setup.admin_api_security - sylius.behat.context.setup.admin_api_security
- sylius.behat.context.setup.channel - sylius.behat.context.setup.channel
- sylius.behat.context.setup.locale
- sylius.behat.context.setup.product - sylius.behat.context.setup.product
- sylius.behat.context.setup.product_association - sylius.behat.context.setup.product_association
- sylius.behat.context.setup.product_attribute
- sylius.behat.context.setup.product_option - sylius.behat.context.setup.product_option
- sylius.behat.context.setup.product_taxon
- sylius.behat.context.setup.shipping_category - sylius.behat.context.setup.shipping_category
- sylius.behat.context.setup.taxation
- sylius.behat.context.setup.taxonomy - sylius.behat.context.setup.taxonomy
- Sylius\Behat\Context\Setup\CatalogPromotionContext
- Sylius\Behat\Context\Setup\PriceHistoryContext
- Sylius\Calendar\Tests\Behat\Context\Setup\CalendarContext
- sylius.behat.context.api.admin.managing_products - sylius.behat.context.api.admin.managing_products
- Sylius\Behat\Context\Api\Admin\BrowsingCatalogPromotionProductVariantsContext
filters: filters:
tags: "@viewing_product_in_admin_panel&&@api" tags: "@viewing_product_in_admin_panel&&@api"

View file

@ -16,9 +16,9 @@ default:
- sylius.behat.context.transform.product_variant - sylius.behat.context.transform.product_variant
- sylius.behat.context.transform.shared_storage - sylius.behat.context.transform.shared_storage
- sylius.behat.context.transform.taxon - sylius.behat.context.transform.taxon
- Sylius\Behat\Context\Transform\CatalogPromotionContext
- sylius.behat.context.setup.admin_user - sylius.behat.context.setup.admin_user
- sylius.behat.context.setup.admin_security
- sylius.behat.context.setup.channel - sylius.behat.context.setup.channel
- sylius.behat.context.setup.customer - sylius.behat.context.setup.customer
- sylius.behat.context.setup.locale - sylius.behat.context.setup.locale
@ -27,7 +27,7 @@ default:
- sylius.behat.context.setup.product_attribute - sylius.behat.context.setup.product_attribute
- sylius.behat.context.setup.product_review - sylius.behat.context.setup.product_review
- sylius.behat.context.setup.product_taxon - sylius.behat.context.setup.product_taxon
- sylius.behat.context.setup.shop_security - sylius.behat.context.setup.shop_api_security
- sylius.behat.context.setup.taxonomy - sylius.behat.context.setup.taxonomy
- Sylius\Behat\Context\Setup\CatalogPromotionContext - Sylius\Behat\Context\Setup\CatalogPromotionContext
- Sylius\Behat\Context\Setup\PriceHistoryContext - Sylius\Behat\Context\Setup\PriceHistoryContext

View file

@ -9,7 +9,9 @@ default:
- sylius.behat.context.hook.session - sylius.behat.context.hook.session
- sylius.behat.context.transform.channel - sylius.behat.context.transform.channel
- sylius.behat.context.transform.currency
- sylius.behat.context.transform.lexical - sylius.behat.context.transform.lexical
- sylius.behat.context.transform.locale
- sylius.behat.context.transform.product - sylius.behat.context.transform.product
- sylius.behat.context.transform.product_association_type - sylius.behat.context.transform.product_association_type
- sylius.behat.context.transform.product_option - sylius.behat.context.transform.product_option
@ -19,19 +21,26 @@ default:
- sylius.behat.context.transform.shipping_category - sylius.behat.context.transform.shipping_category
- sylius.behat.context.transform.tax_category - sylius.behat.context.transform.tax_category
- sylius.behat.context.transform.taxon - sylius.behat.context.transform.taxon
- Sylius\Behat\Context\Transform\CatalogPromotionContext
- sylius.behat.context.setup.admin_security - sylius.behat.context.setup.admin_security
- sylius.behat.context.setup.channel - sylius.behat.context.setup.channel
- sylius.behat.context.setup.locale
- sylius.behat.context.setup.product - sylius.behat.context.setup.product
- sylius.behat.context.setup.product_association - sylius.behat.context.setup.product_association
- sylius.behat.context.setup.product_attribute
- sylius.behat.context.setup.product_option - sylius.behat.context.setup.product_option
- sylius.behat.context.setup.product_taxon - sylius.behat.context.setup.product_taxon
- sylius.behat.context.setup.shipping_category - sylius.behat.context.setup.shipping_category
- sylius.behat.context.setup.taxonomy
- sylius.behat.context.setup.taxation - sylius.behat.context.setup.taxation
- sylius.behat.context.setup.taxonomy
- Sylius\Behat\Context\Setup\CatalogPromotionContext
- Sylius\Behat\Context\Setup\PriceHistoryContext
- Sylius\Calendar\Tests\Behat\Context\Setup\CalendarContext
- sylius.behat.context.ui.admin.managing_product_attributes - sylius.behat.context.ui.admin.managing_product_attributes
- sylius.behat.context.ui.admin.product_showpage - sylius.behat.context.ui.admin.product_showpage
- sylius.behat.context.ui.shop.browsing_product
filters: filters:
tags: "@viewing_product_in_admin_panel&&@ui" tags: "@viewing_product_in_admin_panel&&@ui"

View file

@ -44,8 +44,6 @@ default:
- Sylius\Behat\Context\Setup\PriceHistoryContext - Sylius\Behat\Context\Setup\PriceHistoryContext
- Sylius\Calendar\Tests\Behat\Context\Setup\CalendarContext - Sylius\Calendar\Tests\Behat\Context\Setup\CalendarContext
- sylius.behat.context.ui.admin.managing_product_attributes
- sylius.behat.context.ui.admin.product_showpage
- sylius.behat.context.ui.channel - sylius.behat.context.ui.channel
- sylius.behat.context.ui.shop.locale - sylius.behat.context.ui.shop.locale
- sylius.behat.context.ui.shop.product - sylius.behat.context.ui.shop.product

View file

@ -23,7 +23,7 @@ use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
/** @experimental */ /** @experimental */
final class CatalogPromotionChannelFilter extends AbstractContextAwareFilter final class ChannelsAwareChannelFilter extends AbstractContextAwareFilter
{ {
public function __construct( public function __construct(
private IriConverterInterface $iriConverter, private IriConverterInterface $iriConverter,

View file

@ -42,7 +42,7 @@ final class TranslationOrderNameAndLocaleFilter extends AbstractContextAwareFilt
$queryBuilder $queryBuilder
->addSelect('translation') ->addSelect('translation')
->innerJoin( ->leftJoin(
sprintf('%s.translations', $rootAlias), sprintf('%s.translations', $rootAlias),
'translation', 'translation',
'WITH', 'WITH',

View file

@ -28,7 +28,7 @@
<attribute>sylius.api.catalog_promotion_enabled_filter</attribute> <attribute>sylius.api.catalog_promotion_enabled_filter</attribute>
<attribute>sylius.api.catalog_promotion_start_date_filter</attribute> <attribute>sylius.api.catalog_promotion_start_date_filter</attribute>
<attribute>sylius.api.catalog_promotion_end_date_filter</attribute> <attribute>sylius.api.catalog_promotion_end_date_filter</attribute>
<attribute>Sylius\Bundle\ApiBundle\Filter\Doctrine\CatalogPromotionChannelFilter</attribute> <attribute>Sylius\Bundle\ApiBundle\Filter\Doctrine\ChannelsAwareChannelFilter</attribute>
<attribute>sylius.api.order_filter.code</attribute> <attribute>sylius.api.order_filter.code</attribute>
<attribute>sylius.api.order_filter.name</attribute> <attribute>sylius.api.order_filter.name</attribute>
<attribute>sylius.api.order_filter.start_date</attribute> <attribute>sylius.api.order_filter.start_date</attribute>

View file

@ -30,6 +30,7 @@
<attribute>sylius.api.product_name_filter</attribute> <attribute>sylius.api.product_name_filter</attribute>
<attribute>sylius.api.product_order_filter</attribute> <attribute>sylius.api.product_order_filter</attribute>
<attribute>sylius.api.product_taxon_code_filter</attribute> <attribute>sylius.api.product_taxon_code_filter</attribute>
<attribute>Sylius\Bundle\ApiBundle\Filter\Doctrine\ChannelsAwareChannelFilter</attribute>
<attribute>Sylius\Bundle\ApiBundle\Filter\Doctrine\TranslationOrderNameAndLocaleFilter</attribute> <attribute>Sylius\Bundle\ApiBundle\Filter\Doctrine\TranslationOrderNameAndLocaleFilter</attribute>
</attribute> </attribute>
<attribute name="normalization_context"> <attribute name="normalization_context">

View file

@ -18,9 +18,67 @@
<resource class="%sylius.model.product_association.class%" shortName="ProductAssociation"> <resource class="%sylius.model.product_association.class%" shortName="ProductAssociation">
<attribute name="validation_groups">sylius</attribute> <attribute name="validation_groups">sylius</attribute>
<collectionOperations /> <collectionOperations>
<collectionOperation name="admin_get">
<attribute name="method">GET</attribute>
<attribute name="path">/admin/product-associations</attribute>
<attribute name="filters">
<attribute>sylius.api.product_association_filter</attribute>
</attribute>
<attribute name="normalization_context">
<attribute name="groups">
<attribute>admin:product_association:read</attribute>
</attribute>
</attribute>
</collectionOperation>
<collectionOperation name="admin_post">
<attribute name="method">POST</attribute>
<attribute name="path">/admin/product-associations</attribute>
<attribute name="denormalization_context">
<attribute name="groups">
<attribute>admin:product_association:create</attribute>
</attribute>
</attribute>
<attribute name="normalization_context">
<attribute name="groups">
<attribute>admin:product_association:read</attribute>
</attribute>
</attribute>
</collectionOperation>
</collectionOperations>
<itemOperations> <itemOperations>
<itemOperation name="admin_get">
<attribute name="method">GET</attribute>
<attribute name="path">/admin/product-associations/{id}</attribute>
<attribute name="normalization_context">
<attribute name="groups">
<attribute>admin:product_association:read</attribute>
</attribute>
</attribute>
</itemOperation>
<itemOperation name="admin_put">
<attribute name="method">PUT</attribute>
<attribute name="path">/admin/product-associations/{id}</attribute>
<attribute name="denormalization_context">
<attribute name="groups">
<attribute>admin:product_association:update</attribute>
</attribute>
</attribute>
<attribute name="normalization_context">
<attribute name="groups">
<attribute>admin:product_association:read</attribute>
</attribute>
</attribute>
</itemOperation>
<itemOperation name="admin_delete">
<attribute name="method">DELETE</attribute>
<attribute name="path">/admin/product-associations/{id}</attribute>
</itemOperation>
<itemOperation name="shop_get"> <itemOperation name="shop_get">
<attribute name="method">GET</attribute> <attribute name="method">GET</attribute>
<attribute name="path">/shop/product-associations/{id}</attribute> <attribute name="path">/shop/product-associations/{id}</attribute>
@ -34,6 +92,7 @@
<property name="id" identifier="true" writable="false" /> <property name="id" identifier="true" writable="false" />
<property name="type" identifier="false" required="true" /> <property name="type" identifier="false" required="true" />
<property name="owner" required="true" />
<property name="associatedProducts" required="true" /> <property name="associatedProducts" required="true" />
</resource> </resource>
</resources> </resources>

View file

@ -26,6 +26,9 @@
<collectionOperation name="admin_get"> <collectionOperation name="admin_get">
<attribute name="method">GET</attribute> <attribute name="method">GET</attribute>
<attribute name="path">/admin/product-attributes</attribute> <attribute name="path">/admin/product-attributes</attribute>
<attribute name="filters">
<attribute>sylius.api.order_filter.position</attribute>
</attribute>
<attribute name="normalization_context"> <attribute name="normalization_context">
<attribute name="groups">admin:product_attribute:read</attribute> <attribute name="groups">admin:product_attribute:read</attribute>
</attribute> </attribute>

View file

@ -1,5 +1,16 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<!--
This file is part of the Sylius package.
(c) Sylius Sp. z o.o.
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<resources xmlns="https://api-platform.com/schema/metadata" <resources xmlns="https://api-platform.com/schema/metadata"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://api-platform.com/schema/metadata https://api-platform.com/schema/metadata/metadata-2.0.xsd" xsi:schemaLocation="https://api-platform.com/schema/metadata https://api-platform.com/schema/metadata/metadata-2.0.xsd"
@ -7,16 +18,21 @@
<resource class="%sylius.model.product_taxon.class%" shortName="ProductTaxon"> <resource class="%sylius.model.product_taxon.class%" shortName="ProductTaxon">
<attribute name="validation_groups">sylius</attribute> <attribute name="validation_groups">sylius</attribute>
<attribute name="order">
<attribute name="position">ASC</attribute>
</attribute>
<collectionOperations> <collectionOperations>
<collectionOperation name="admin_get"> <collectionOperation name="admin_get">
<attribute name="method">GET</attribute> <attribute name="method">GET</attribute>
<attribute name="path">/admin/product-taxons</attribute> <attribute name="path">/admin/product-taxons</attribute>
<attribute name="normalization_context">
<attribute name="groups">admin:product_taxon:read</attribute>
</attribute>
<attribute name="filters"> <attribute name="filters">
<attribute>sylius.api.search_filter.product.code</attribute> <attribute>sylius.api.search_filter.product.code</attribute>
<attribute>sylius.api.search_filter.taxon.code</attribute> <attribute>sylius.api.search_filter.taxon.code</attribute>
<attribute>sylius.api.order_filter.position</attribute>
</attribute>
<attribute name="normalization_context">
<attribute name="groups">admin:product_taxon:read</attribute>
</attribute> </attribute>
</collectionOperation> </collectionOperation>
<collectionOperation name="admin_post"> <collectionOperation name="admin_post">

View file

@ -16,12 +16,14 @@
<group>admin:catalog_promotion:read</group> <group>admin:catalog_promotion:read</group>
<group>admin:catalog_promotion:create</group> <group>admin:catalog_promotion:create</group>
<group>admin:catalog_promotion:update</group> <group>admin:catalog_promotion:update</group>
<group>admin:product_variant:read</group>
<group>shop:catalog_promotion:read</group> <group>shop:catalog_promotion:read</group>
</attribute> </attribute>
<attribute name="code"> <attribute name="code">
<group>admin:catalog_promotion:read</group> <group>admin:catalog_promotion:read</group>
<group>admin:catalog_promotion:create</group> <group>admin:catalog_promotion:create</group>
<group>admin:product_variant:read</group>
</attribute> </attribute>
<attribute name="name"> <attribute name="name">

View file

@ -42,5 +42,9 @@
<attribute name="lowestPriceBeforeDiscount"> <attribute name="lowestPriceBeforeDiscount">
<group>admin:product_variant:read</group> <group>admin:product_variant:read</group>
</attribute> </attribute>
<attribute name="appliedPromotions">
<group>admin:product_variant:read</group>
</attribute>
</class> </class>
</serializer> </serializer>

View file

@ -86,6 +86,7 @@
<group>shop:product:read</group> <group>shop:product:read</group>
</attribute> </attribute>
<attribute name="associations"> <attribute name="associations">
<group>admin:product:read</group>
<group>shop:product:read</group> <group>shop:product:read</group>
</attribute> </attribute>
<attribute name="attributes"> <attribute name="attributes">

View file

@ -20,9 +20,18 @@
<group>shop:product_association:read</group> <group>shop:product_association:read</group>
</attribute> </attribute>
<attribute name="type"> <attribute name="type">
<group>admin:product_association:read</group>
<group>admin:product_association:create</group>
<group>shop:product_association:read</group> <group>shop:product_association:read</group>
</attribute> </attribute>
<attribute name="owner">
<group>admin:product_association:read</group>
<group>admin:product_association:create</group>
</attribute>
<attribute name="associatedProducts"> <attribute name="associatedProducts">
<group>admin:product_association:read</group>
<group>admin:product_association:create</group>
<group>admin:product_association:update</group>
<group>shop:product_association:read</group> <group>shop:product_association:read</group>
</attribute> </attribute>
</class> </class>

View file

@ -33,6 +33,14 @@
<tag name="api_platform.filter" /> <tag name="api_platform.filter" />
</service> </service>
<service id="sylius.api.product_association_filter" parent="api_platform.doctrine.orm.search_filter" public="true">
<argument type="collection">
<argument key="owner.code">partial</argument>
<argument key="type.code">partial</argument>
</argument>
<tag name="api_platform.filter" />
</service>
<service id="sylius.api.product_taxon_code_filter" parent="api_platform.doctrine.orm.search_filter" public="true"> <service id="sylius.api.product_taxon_code_filter" parent="api_platform.doctrine.orm.search_filter" public="true">
<argument type="collection"> <argument type="collection">
<argument key="productTaxons.taxon.code">exact</argument> <argument key="productTaxons.taxon.code">exact</argument>
@ -71,6 +79,13 @@
<tag name="api_platform.filter" /> <tag name="api_platform.filter" />
</service> </service>
<service id="sylius.api.order_filter.position" parent="api_platform.doctrine.orm.order_filter" public="true">
<argument type="collection">
<argument key="position" />
</argument>
<tag name="api_platform.filter" />
</service>
<service id="sylius.api.product_variant_product_filter" parent="api_platform.doctrine.orm.search_filter" public="true"> <service id="sylius.api.product_variant_product_filter" parent="api_platform.doctrine.orm.search_filter" public="true">
<argument type="collection"> <argument type="collection">
<argument key="product">exact</argument> <argument key="product">exact</argument>
@ -246,7 +261,7 @@
<tag name="api_platform.filter" /> <tag name="api_platform.filter" />
</service> </service>
<service id="Sylius\Bundle\ApiBundle\Filter\Doctrine\CatalogPromotionChannelFilter" public="true"> <service id="Sylius\Bundle\ApiBundle\Filter\Doctrine\ChannelsAwareChannelFilter" public="true">
<argument type="service" id="api_platform.symfony.iri_converter" /> <argument type="service" id="api_platform.symfony.iri_converter" />
<argument type="service" id="doctrine" /> <argument type="service" id="doctrine" />
<tag name="api_platform.filter" /> <tag name="api_platform.filter" />
@ -281,13 +296,6 @@
<tag name="api_platform.filter" /> <tag name="api_platform.filter" />
</service> </service>
<service id="sylius.api.product_attribute_order_filter" parent="api_platform.doctrine.orm.order_filter" public="true">
<argument type="collection">
<argument key="position" />
</argument>
<tag name="api_platform.filter" />
</service>
<service id="sylius.api.customer_group_filter" parent="api_platform.doctrine.orm.search_filter" public="true"> <service id="sylius.api.customer_group_filter" parent="api_platform.doctrine.orm.search_filter" public="true">
<argument type="collection"> <argument type="collection">
<argument key="group.name">exact</argument> <argument key="group.name">exact</argument>

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file is part of the Sylius package.
(c) Sylius Sp. z o.o.
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
<class name="Sylius\Component\Product\Model\ProductAssociation">
<constraint name="Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity">
<option name="fields">
<value>owner</value>
<value>type</value>
</option>
<option name="errorPath">owner</option>
<option name="message">sylius.association.unique</option>
<option name="groups">sylius</option>
</constraint>
<property name="type">
<constraint name="NotBlank">
<option name="message">sylius.association.type.not_blank</option>
<option name="groups">sylius</option>
</constraint>
</property>
<property name="owner">
<constraint name="NotBlank">
<option name="message">sylius.association.owner.not_blank</option>
<option name="groups">sylius</option>
</constraint>
</property>
</class>
</constraint-mapping>

View file

@ -47,6 +47,12 @@ sylius:
unique: The option value with given code already exists. unique: The option value with given code already exists.
value: value:
not_blank: Please enter option value. not_blank: Please enter option value.
association:
unique: An association with this owner and type already exists.
type:
not_blank: Please enter association type.
owner:
not_blank: Please enter association owner.
association_type: association_type:
name: name:
not_blank: Please enter association type name. not_blank: Please enter association type name.

View file

@ -0,0 +1,265 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Api\Admin;
use Sylius\Component\Product\Model\ProductAssociationInterface;
use Sylius\Tests\Api\JsonApiTestCase;
use Symfony\Component\HttpFoundation\Response;
final class ProductAssociationsTest extends JsonApiTestCase
{
/** @test */
public function it_gets_product_association(): void
{
$fixtures = $this->loadFixturesFromFiles([
'authentication/api_administrator.yaml',
'product/product_with_many_locales.yaml',
]);
/** @var ProductAssociationInterface $association */
$association = $fixtures['product_association'];
$this->client->request(
method: 'GET',
uri: sprintf('/api/v2/admin/product-associations/%s', $association->getId()),
server: $this
->headerBuilder()
->withJsonLdAccept()
->withAdminUserAuthorization('api@example.com')
->build()
,
);
$this->assertResponse(
$this->client->getResponse(),
'admin/product_association/get_product_association_response',
Response::HTTP_OK,
);
}
/** @test */
public function it_returns_nothing_if_association_not_found(): void
{
$this->loadFixturesFromFiles([
'authentication/api_administrator.yaml',
'product/product_with_many_locales.yaml',
]);
$this->client->request(
method: 'GET',
uri: '/api/v2/admin/product-associations/nope',
server: $this
->headerBuilder()
->withJsonLdAccept()
->withAdminUserAuthorization('api@example.com')
->build()
,
);
$response = $this->client->getResponse();
$this->assertSame(Response::HTTP_NOT_FOUND, $response->getStatusCode());
}
/** @test */
public function it_returns_product_association_collection(): void
{
$this->loadFixturesFromFiles([
'product/product_with_many_locales.yaml',
'authentication/api_administrator.yaml'
]);
$this->client->request(
method: 'GET',
uri: '/api/v2/admin/product-associations',
server: $this
->headerBuilder()
->withJsonLdAccept()
->withAdminUserAuthorization('api@example.com')
->build()
,
);
$this->assertResponse(
$this->client->getResponse(),
'admin/product_association/get_product_association_collection_response',
Response::HTTP_OK,
);
}
/** @test */
public function it_creates_product_association(): void
{
$this->loadFixturesFromFiles([
'product/products_with_associations.yaml',
'authentication/api_administrator.yaml',
]);
$this->client->request(
method: 'POST',
uri: '/api/v2/admin/product-associations',
server: $this
->headerBuilder()
->withJsonLdContentType()
->withJsonLdAccept()
->withAdminUserAuthorization('api@example.com')
->build()
,
content: json_encode([
'type' => '/api/v2/admin/product-association-types/similar_products',
'owner' => '/api/v2/admin/products/CUP',
'associatedProducts' => [
'/api/v2/admin/products/MUG',
],
], \JSON_THROW_ON_ERROR),
);
$this->assertResponse(
$this->client->getResponse(),
'admin/product_association/post_product_association_response',
Response::HTTP_CREATED,
);
}
/** @test */
public function it_updates_product_association(): void
{
$fixtures = $this->loadFixturesFromFiles([
'product/products_with_associations.yaml',
'authentication/api_administrator.yaml',
]);
/** @var ProductAssociationInterface $association */
$association = $fixtures['product_association'];
$this->client->request(
method: 'PUT',
uri: sprintf('/api/v2/admin/product-associations/%s', $association->getId()),
server: $this
->headerBuilder()
->withJsonLdContentType()
->withJsonLdAccept()
->withAdminUserAuthorization('api@example.com')
->build()
,
content: json_encode([
'associatedProducts' => [
'/api/v2/admin/products/TANKARD',
],
], \JSON_THROW_ON_ERROR),
);
$this->assertResponse(
$this->client->getResponse(),
'admin/product_association/put_product_association_response',
Response::HTTP_OK,
);
}
/** @test */
public function it_deletes_product_association(): void
{
$fixtures = $this->loadFixturesFromFiles([
'product/products_with_associations.yaml',
'authentication/api_administrator.yaml',
]);
$header = $this
->headerBuilder()
->withJsonLdAccept()
->withAdminUserAuthorization('api@example.com')
->build()
;
/** @var ProductAssociationInterface $association */
$association = $fixtures['product_association'];
$associationId = $association->getId();
$this->client->request(
method: 'DELETE',
uri: sprintf('/api/v2/admin/product-associations/%s', $associationId),
server: $header,
);
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NO_CONTENT);
$this->client->request(
method: 'GET',
uri: sprintf('/api/v2/admin/product-associations/%s', $associationId),
server: $header,
);
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
}
/** @test */
public function it_does_not_create_product_association_without_required_data(): void
{
$this->loadFixturesFromFiles([
'product/products_with_associations.yaml',
'authentication/api_administrator.yaml',
]);
$this->client->request(
method: 'POST',
uri: '/api/v2/admin/product-associations',
server: $this
->headerBuilder()
->withJsonLdContentType()
->withJsonLdAccept()
->withAdminUserAuthorization('api@example.com')
->build()
,
content: '{}',
);
$this->assertResponse(
$this->client->getResponse(),
'admin/product_association/post_product_association_without_required_data_response',
Response::HTTP_UNPROCESSABLE_ENTITY,
);
}
/** @test */
public function it_does_not_create_duplicated_product_association(): void
{
$fixtures = $this->loadFixturesFromFiles([
'product/products_with_associations.yaml',
'authentication/api_administrator.yaml',
]);
/** @var ProductAssociationInterface $association */
$association = $fixtures['product_association'];
$this->client->request(
method: 'POST',
uri: '/api/v2/admin/product-associations',
server: $this
->headerBuilder()
->withJsonLdContentType()
->withJsonLdAccept()
->withAdminUserAuthorization('api@example.com')
->build()
,
content: json_encode([
'type' => sprintf('/api/v2/admin/product-association-types/%s', $association->getType()->getCode()),
'owner' => sprintf('/api/v2/admin/products/%s', $association->getOwner()->getCode()),
], \JSON_THROW_ON_ERROR),
);
$this->assertResponse(
$this->client->getResponse(),
'admin/product_association/post_duplicated_association_product_response',
Response::HTTP_UNPROCESSABLE_ENTITY,
);
}
}

View file

@ -9,7 +9,7 @@ Sylius\Component\Core\Model\Product:
translations: translations:
en_US: '@product_translation_mug_en_US' en_US: '@product_translation_mug_en_US'
pl_PL: '@product_translation_mug_pl_PL' pl_PL: '@product_translation_mug_pl_PL'
attributes: attributes:
- '@product_attribute_value_material_en_US' - '@product_attribute_value_material_en_US'
- '@product_attribute_value_material_pl_PL' - '@product_attribute_value_material_pl_PL'
product_cap: product_cap:
@ -95,7 +95,7 @@ Sylius\Component\Product\Model\ProductVariantTranslation:
locale: 'en_US' locale: 'en_US'
name: 'Red Mug' name: 'Red Mug'
translatable: '@product_variant_mug_red' translatable: '@product_variant_mug_red'
Sylius\Component\Core\Model\ChannelPricing: Sylius\Component\Core\Model\ChannelPricing:
channel_pricing_mug_blue_web: channel_pricing_mug_blue_web:
channelCode: 'WEB' channelCode: 'WEB'
@ -114,7 +114,7 @@ Sylius\Component\Core\Model\ChannelPricing:
channel_pricing_cap_red_web: channel_pricing_cap_red_web:
channelCode: 'WEB' channelCode: 'WEB'
price: 2000 price: 2000
Sylius\Component\Product\Model\ProductOption: Sylius\Component\Product\Model\ProductOption:
product_option_color: product_option_color:
code: 'COLOR' code: 'COLOR'
@ -228,3 +228,21 @@ Sylius\Component\Core\Model\OrderItem:
order_item: order_item:
variant: '@product_variant_cap_red' variant: '@product_variant_cap_red'
order: '@cart_with_items' order: '@cart_with_items'
Sylius\Component\Product\Model\ProductAssociation:
product_association:
type: '@product_association_type'
owner: '@product_mug'
associatedProducts: ['@product_cap']
Sylius\Component\Product\Model\ProductAssociationType:
product_association_type:
code: 'similar_products'
translations:
en_US: '@product_association_type_translation'
Sylius\Component\Product\Model\ProductAssociationTypeTranslation:
product_association_type_translation:
name: 'Similar products'
locale: 'en_US'
translatable: '@product_association_type'

View file

@ -33,6 +33,12 @@ Sylius\Component\Core\Model\Product:
currentLocale: 'en_US' currentLocale: 'en_US'
translations: translations:
en_US: '@product_translation_cup_en_US' en_US: '@product_translation_cup_en_US'
product_tankard:
code: 'TANKARD'
channels: [ '@channel_web' ]
currentLocale: 'en_US'
translations:
en_US: '@product_translation_tankard_en_US'
product_hat: product_hat:
code: 'HAT' code: 'HAT'
channels: [ '@channel_web' ] channels: [ '@channel_web' ]
@ -56,6 +62,13 @@ Sylius\Component\Core\Model\ProductTranslation:
description: 'Short cup description' description: 'Short cup description'
shortDescription: 'Cup' shortDescription: 'Cup'
translatable: '@product_cup' translatable: '@product_cup'
product_translation_tankard_en_US:
slug: 'Tankard'
locale: 'en_US'
name: 'Tankard'
description: 'Tankard description'
shortDescription: 'Tankard'
translatable: '@product_tankard'
product_translation_hat_en_US: product_translation_hat_en_US:
slug: 'Hat' slug: 'Hat'
locale: 'en_US' locale: 'en_US'

View file

@ -24,6 +24,9 @@
"\/api\/v2\/admin\/product-reviews\/@integer@", "\/api\/v2\/admin\/product-reviews\/@integer@",
"\/api\/v2\/admin\/product-reviews\/@integer@" "\/api\/v2\/admin\/product-reviews\/@integer@"
], ],
"associations": [
"\/api\/v2\/admin\/product-associations\/@integer@"
],
"attributes": [ "attributes": [
{ {
"@id": "\/api\/v2\/admin\/product-attribute-values\/@integer@", "@id": "\/api\/v2\/admin\/product-attribute-values\/@integer@",

View file

@ -22,6 +22,7 @@
"\/api\/v2\/admin\/channels\/WEB" "\/api\/v2\/admin\/channels\/WEB"
], ],
"reviews": [], "reviews": [],
"associations": [],
"attributes": [], "attributes": [],
"averageRating": 0, "averageRating": 0,
"images": [], "images": [],
@ -66,6 +67,9 @@
"\/api\/v2\/admin\/product-reviews\/@integer@", "\/api\/v2\/admin\/product-reviews\/@integer@",
"\/api\/v2\/admin\/product-reviews\/@integer@" "\/api\/v2\/admin\/product-reviews\/@integer@"
], ],
"associations": [
"\/api\/v2\/admin\/product-associations\/@integer@"
],
"attributes": [ "attributes": [
{ {
"@id": "\/api\/v2\/admin\/product-attribute-values\/@integer@", "@id": "\/api\/v2\/admin\/product-attribute-values\/@integer@",
@ -139,6 +143,7 @@
"\/api\/v2\/admin\/channels\/WEB" "\/api\/v2\/admin\/channels\/WEB"
], ],
"reviews": [], "reviews": [],
"associations": [],
"attributes": [], "attributes": [],
"averageRating": 0, "averageRating": 0,
"images": [], "images": [],
@ -162,7 +167,7 @@
"hydra:totalItems": 3, "hydra:totalItems": 3,
"hydra:search": { "hydra:search": {
"@type": "hydra:IriTemplate", "@type": "hydra:IriTemplate",
"hydra:template": "\/api\/v2\/admin\/products{?translations.name,order[code],order[createdAt],productTaxons.taxon.code,productTaxons.taxon.code[],order[translation.name],localeCode for order[translation.name]}", "hydra:template": "\/api\/v2\/admin\/products{?translations.name,order[code],order[createdAt],productTaxons.taxon.code,productTaxons.taxon.code[],channel,order[translation.name],localeCode for order[translation.name]}",
"hydra:variableRepresentation": "BasicRepresentation", "hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [ "hydra:mapping": [
{ {
@ -195,6 +200,12 @@
"property": "productTaxons.taxon.code", "property": "productTaxons.taxon.code",
"required": false "required": false
}, },
{
"@type": "IriTemplateMapping",
"variable": "channel",
"property": "channels",
"required": false
},
{ {
"@type": "IriTemplateMapping", "@type": "IriTemplateMapping",
"variable": "order[translation.name]", "variable": "order[translation.name]",

View file

@ -16,6 +16,7 @@
"\/api\/v2\/admin\/channels\/WEB_GB" "\/api\/v2\/admin\/channels\/WEB_GB"
], ],
"reviews": [], "reviews": [],
"associations": [],
"attributes": [ "attributes": [
{ {
"@id": "\/api\/v2\/admin\/product-attribute-values\/@integer@", "@id": "\/api\/v2\/admin\/product-attribute-values\/@integer@",

View file

@ -24,6 +24,9 @@
"\/api\/v2\/admin\/product-reviews\/@integer@", "\/api\/v2\/admin\/product-reviews\/@integer@",
"\/api\/v2\/admin\/product-reviews\/@integer@" "\/api\/v2\/admin\/product-reviews\/@integer@"
], ],
"associations": [
"\/api\/v2\/admin\/product-associations\/@integer@"
],
"attributes": [ "attributes": [
{ {
"@id": "\/api\/v2\/admin\/product-attribute-values\/@integer@", "@id": "\/api\/v2\/admin\/product-attribute-values\/@integer@",

View file

@ -0,0 +1,36 @@
{
"@context": "\/api\/v2\/contexts\/ProductAssociation",
"@id": "\/api\/v2\/admin\/product-associations",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "\/api\/v2\/admin\/product-associations\/@integer@",
"@type": "ProductAssociation",
"type": "\/api\/v2\/admin\/product-association-types\/similar_products",
"owner": "\/api\/v2\/admin\/products\/MUG",
"associatedProducts": [
"\/api\/v2\/admin\/products\/CUP"
]
}
],
"hydra:totalItems": 1,
"hydra:search": {
"@type": "hydra:IriTemplate",
"hydra:template": "\/api\/v2\/admin\/product-associations{?owner.code,type.code}",
"hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [
{
"@type": "IriTemplateMapping",
"variable": "owner.code",
"property": "owner.code",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "type.code",
"property": "type.code",
"required": false
}
]
}
}

View file

@ -0,0 +1,10 @@
{
"@context": "\/api\/v2\/contexts\/ProductAssociation",
"@id": "\/api\/v2\/admin\/product-associations\/@integer@",
"@type": "ProductAssociation",
"type": "\/api\/v2\/admin\/product-association-types\/similar_products",
"owner": "\/api\/v2\/admin\/products\/MUG",
"associatedProducts": [
"\/api\/v2\/admin\/products\/CUP"
]
}

View file

@ -0,0 +1,13 @@
{
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
"@type": "ConstraintViolationList",
"hydra:title": "An error occurred",
"hydra:description": "owner: An association with this owner and type already exists.",
"violations": [
{
"propertyPath": "owner",
"message": "An association with this owner and type already exists.",
"code": @string@
}
]
}

View file

@ -0,0 +1,10 @@
{
"@context": "\/api\/v2\/contexts\/ProductAssociation",
"@id": "\/api\/v2\/admin\/product-associations\/@integer@",
"@type": "ProductAssociation",
"type": "\/api\/v2\/admin\/product-association-types\/similar_products",
"owner": "\/api\/v2\/admin\/products\/CUP",
"associatedProducts": [
"\/api\/v2\/admin\/products\/MUG"
]
}

View file

@ -0,0 +1,18 @@
{
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
"@type": "ConstraintViolationList",
"hydra:title": "An error occurred",
"hydra:description": "type: Please enter association type.\nowner: Please enter association owner.",
"violations": [
{
"propertyPath": "type",
"message": "Please enter association type.",
"code": @string@
},
{
"propertyPath": "owner",
"message": "Please enter association owner.",
"code": @string@
}
]
}

View file

@ -0,0 +1,10 @@
{
"@context": "\/api\/v2\/contexts\/ProductAssociation",
"@id": "\/api\/v2\/admin\/product-associations\/@integer@",
"@type": "ProductAssociation",
"type": "\/api\/v2\/admin\/product-association-types\/similar_products",
"owner": "\/api\/v2\/admin\/products\/MUG",
"associatedProducts": [
"\/api\/v2\/admin\/products\/TANKARD"
]
}

View file

@ -184,5 +184,18 @@
"type": "text" "type": "text"
} }
], ],
"hydra:totalItems": 9 "hydra:totalItems": 9,
"hydra:search": {
"@type": "hydra:IriTemplate",
"hydra:template": "\/api\/v2\/admin\/product-attributes{?order[position]}",
"hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [
{
"@type": "IriTemplateMapping",
"variable": "order[position]",
"property": "position",
"required": false
}
]
}
} }

View file

@ -23,7 +23,7 @@
"hydra:totalItems": 2, "hydra:totalItems": 2,
"hydra:search": { "hydra:search": {
"@type": "hydra:IriTemplate", "@type": "hydra:IriTemplate",
"hydra:template": "\/api\/v2\/admin\/product-taxons{?product.code,product.code[],taxon.code,taxon.code[]}", "hydra:template": "\/api\/v2\/admin\/product-taxons{?product.code,product.code[],taxon.code,taxon.code[],order[position]}",
"hydra:variableRepresentation": "BasicRepresentation", "hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [ "hydra:mapping": [
{ {
@ -49,6 +49,12 @@
"variable": "taxon.code[]", "variable": "taxon.code[]",
"property": "taxon.code", "property": "taxon.code",
"required": false "required": false
},
{
"@type": "IriTemplateMapping",
"variable": "order[position]",
"property": "position",
"required": false
} }
] ]
} }

View file

@ -15,7 +15,8 @@
"price": 2000, "price": 2000,
"originalPrice": null, "originalPrice": null,
"lowestPriceBeforeDiscount": null, "lowestPriceBeforeDiscount": null,
"minimumPrice": 0 "minimumPrice": 0,
"appliedPromotions": []
} }
}, },
"translations": { "translations": {

View file

@ -19,7 +19,8 @@
"price": 2000, "price": 2000,
"originalPrice": null, "originalPrice": null,
"lowestPriceBeforeDiscount": null, "lowestPriceBeforeDiscount": null,
"minimumPrice": 0 "minimumPrice": 0,
"appliedPromotions": []
} }
}, },
"translations": { "translations": {
@ -59,7 +60,8 @@
"price": 3000, "price": 3000,
"originalPrice": null, "originalPrice": null,
"lowestPriceBeforeDiscount": null, "lowestPriceBeforeDiscount": null,
"minimumPrice": 0 "minimumPrice": 0,
"appliedPromotions": []
} }
}, },
"translations": { "translations": {

View file

@ -13,7 +13,8 @@
"price": 4000, "price": 4000,
"originalPrice": null, "originalPrice": null,
"minimumPrice": 0, "minimumPrice": 0,
"lowestPriceBeforeDiscount": null "lowestPriceBeforeDiscount": null,
"appliedPromotions": []
} }
}, },
"translations": { "translations": {

View file

@ -15,7 +15,8 @@
"price": 4000, "price": 4000,
"originalPrice": 5000, "originalPrice": 5000,
"minimumPrice": 2000, "minimumPrice": 2000,
"lowestPriceBeforeDiscount": null "lowestPriceBeforeDiscount": null,
"appliedPromotions": []
} }
}, },
"translations": { "translations": {

View file

@ -15,7 +15,8 @@
"price": 3000, "price": 3000,
"originalPrice": 4000, "originalPrice": 4000,
"lowestPriceBeforeDiscount": 2000, "lowestPriceBeforeDiscount": 2000,
"minimumPrice": 500 "minimumPrice": 500,
"appliedPromotions": []
} }
}, },
"translations": { "translations": {

View file

@ -51,68 +51,89 @@
"\/api\/v2\/shop\/product-associations\/@integer@" "\/api\/v2\/shop\/product-associations\/@integer@"
], ],
"defaultVariant": "\/api\/v2\/shop\/product-variants\/MUG_BLUE" "defaultVariant": "\/api\/v2\/shop\/product-variants\/MUG_BLUE"
},
{
"@id": "\/api\/v2\/shop\/products\/TANKARD",
"@type": "Product",
"productTaxons": [],
"mainTaxon": null,
"reviews": [],
"averageRating": @integer@,
"images": [],
"id": @integer@,
"code": "TANKARD",
"variants": [],
"options": [],
"associations": [],
"createdAt": @date@,
"updatedAt": @date@,
"shortDescription": "Tankard",
"name": "Tankard",
"description": "Tankard description",
"slug": "Tankard",
"defaultVariant": null
} }
], ],
"hydra:totalItems": 2, "hydra:totalItems": 3,
"hydra:search": { "hydra:search": {
"@type": "hydra:IriTemplate", "@type": "hydra:IriTemplate",
"hydra:template": "\/api\/v2\/shop\/products{?translations.name,order[code],order[createdAt],productTaxons.taxon.code,productTaxons.taxon.code[],order[price],order[translation.name],localeCode for order[translation.name],taxon}", "hydra:template": "\/api\/v2\/shop\/products{?translations.name,order[code],order[createdAt],productTaxons.taxon.code,productTaxons.taxon.code[],order[price],order[translation.name],localeCode for order[translation.name],taxon}",
"hydra:variableRepresentation": "BasicRepresentation", "hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [ "hydra:mapping": [
{ {
"@type": "IriTemplateMapping", "@type": "IriTemplateMapping",
"variable": "translations.name", "variable": "translations.name",
"property": "translations.name", "property": "translations.name",
"required": false "required": false
}, },
{ {
"@type": "IriTemplateMapping", "@type": "IriTemplateMapping",
"variable": "order[code]", "variable": "order[code]",
"property": "code", "property": "code",
"required": false "required": false
}, },
{ {
"@type": "IriTemplateMapping", "@type": "IriTemplateMapping",
"variable": "order[createdAt]", "variable": "order[createdAt]",
"property": "createdAt", "property": "createdAt",
"required": false "required": false
}, },
{ {
"@type": "IriTemplateMapping", "@type": "IriTemplateMapping",
"variable": "productTaxons.taxon.code", "variable": "productTaxons.taxon.code",
"property": "productTaxons.taxon.code", "property": "productTaxons.taxon.code",
"required": false "required": false
}, },
{ {
"@type": "IriTemplateMapping", "@type": "IriTemplateMapping",
"variable": "productTaxons.taxon.code[]", "variable": "productTaxons.taxon.code[]",
"property": "productTaxons.taxon.code", "property": "productTaxons.taxon.code",
"required": false "required": false
}, },
{ {
"@type": "IriTemplateMapping", "@type": "IriTemplateMapping",
"variable": "order[price]", "variable": "order[price]",
"property": "price", "property": "price",
"required": false "required": false
}, },
{ {
"@type": "IriTemplateMapping", "@type": "IriTemplateMapping",
"variable": "order[translation.name]", "variable": "order[translation.name]",
"property": "translation", "property": "translation",
"required": false "required": false
}, },
{ {
"@type": "IriTemplateMapping", "@type": "IriTemplateMapping",
"variable": "localeCode for order[translation.name]", "variable": "localeCode for order[translation.name]",
"property": "localeCode", "property": "localeCode",
"required": false "required": false
}, },
{ {
"@type": "IriTemplateMapping", "@type": "IriTemplateMapping",
"variable": "taxon", "variable": "taxon",
"property": null, "property": null,
"required": false "required": false
} }
] ]
} }
} }