mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 17:10:53 +00:00
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:
commit
249eb7476f
81 changed files with 1565 additions and 251 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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\\.$#"
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
265
tests/Api/Admin/ProductAssociationsTest.php
Normal file
265
tests/Api/Admin/ProductAssociationsTest.php
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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@",
|
||||||
|
|
|
||||||
|
|
@ -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]",
|
||||||
|
|
|
||||||
|
|
@ -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@",
|
||||||
|
|
|
||||||
|
|
@ -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@",
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -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@
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -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@
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@
|
||||||
"price": 2000,
|
"price": 2000,
|
||||||
"originalPrice": null,
|
"originalPrice": null,
|
||||||
"lowestPriceBeforeDiscount": null,
|
"lowestPriceBeforeDiscount": null,
|
||||||
"minimumPrice": 0
|
"minimumPrice": 0,
|
||||||
|
"appliedPromotions": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"translations": {
|
"translations": {
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@
|
||||||
"price": 4000,
|
"price": 4000,
|
||||||
"originalPrice": null,
|
"originalPrice": null,
|
||||||
"minimumPrice": 0,
|
"minimumPrice": 0,
|
||||||
"lowestPriceBeforeDiscount": null
|
"lowestPriceBeforeDiscount": null,
|
||||||
|
"appliedPromotions": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"translations": {
|
"translations": {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@
|
||||||
"price": 4000,
|
"price": 4000,
|
||||||
"originalPrice": 5000,
|
"originalPrice": 5000,
|
||||||
"minimumPrice": 2000,
|
"minimumPrice": 2000,
|
||||||
"lowestPriceBeforeDiscount": null
|
"lowestPriceBeforeDiscount": null,
|
||||||
|
"appliedPromotions": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"translations": {
|
"translations": {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@
|
||||||
"price": 3000,
|
"price": 3000,
|
||||||
"originalPrice": 4000,
|
"originalPrice": 4000,
|
||||||
"lowestPriceBeforeDiscount": 2000,
|
"lowestPriceBeforeDiscount": 2000,
|
||||||
"minimumPrice": 500
|
"minimumPrice": 500,
|
||||||
|
"appliedPromotions": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"translations": {
|
"translations": {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue