[UPMERGE] 1.13 -> 2.0 (#15666)

This PR has been generated automatically.
For more details see
[upmerge_pr.yaml](/Sylius/Sylius/blob/1.13/.github/workflows/upmerge_pr.yaml).

**Remember!** The upmerge should always be merged with using `Merge pull
request` button.

In case of conflicts, please resolve them manually with usign the
following commands:
```
git fetch upstream
gh pr checkout 
git merge upstream/2.0 --no-commit
```

If you use other name for the upstream remote, please replace `upstream`
with the name of your remote pointing to the `Sylius/Sylius` repository.

Once the conflicts are resolved, please run `git merge --continue` and
change the commit title to
```
Resolve conflicts between 1.13 and 2.0
```
This commit is contained in:
Jacob Tobiasz 2023-12-21 19:12:36 +01:00 committed by GitHub
commit d0b82fd975
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
223 changed files with 4902 additions and 791 deletions

View file

@ -3,7 +3,7 @@ name: Refactor
on:
schedule:
-
cron: "0 2 * * MON" # Run at 2am every Monday
cron: "0 2 * * *" # Run every day at 2am
workflow_dispatch: ~
jobs:

View file

@ -1,44 +0,0 @@
@admin_dashboard
Feature: Statistics dashboard in a single channel
In order to have an overview of my sales
As an Administrator
I want to see overall statistics on my admin dashboard
Background:
Given the store operates on a single channel in "United States"
And the store ships everywhere for Free
And the store allows paying Offline
And the store has a product "Sylius T-Shirt"
And this product has "Red XL" variant priced at "$40.00"
And I am logged in as an administrator
@ui
Scenario: Seeing basic statistics for entire store
Given 3 customers have fulfilled 4 orders placed for total of "$8,566.00"
And 2 more customers have paid 2 orders placed for total of "$459.00"
When I open administration dashboard
Then I should see 6 new orders
And I should see 5 new customers
And there should be total sales of "$9,025.00"
And the average order value should be "$1,504.17"
@ui
Scenario: Statistics include only fulfilled orders that were not cancelled
Given 4 customers have fulfilled 4 orders placed for total of "$5,241.00"
And 2 more customers have placed 2 orders for total of "$459.00"
And 2 customers have added products to the cart for total of "$3,450.00"
And a single customer has placed an order for total of "$1,000.00"
But the customer cancelled this order
When I open administration dashboard
Then I should see 4 new orders
And I should see 9 new customers
And there should be total sales of "$5,241.00"
And the average order value should be "$1,310.25"
@ui
Scenario: Seeing recent orders and customers
Given 2 customers have placed 3 orders for total of "$340.00"
And 2 customers have added products to the cart for total of "$424.00"
When I open administration dashboard
Then I should see 4 new customers in the list
And I should see 3 new orders in the list

View file

@ -1,50 +0,0 @@
@admin_dashboard
Feature: Statistics dashboard per channel
In order to have an overview of my sales
As an Administrator
I want to see overall statistics on my admin dashboard in a specific channel
Background:
Given the store operates on a channel named "Poland"
And there is product "Onion" available in this channel
And the store operates on another channel named "United States"
And there is product "Banana" available in that channel
And the store ships everywhere for Free
And the store allows paying Offline
And I am logged in as an administrator
@ui
Scenario: Seeing basic statistics for the first channel by default
Given 3 customers have fulfilled 4 orders placed for total of "$8,566.00" mostly "Onion" product
And 2 more customers have fulfilled 2 orders placed for total of "$459.00" mostly "Banana" product
When I open administration dashboard
Then I should see 4 new orders
And I should see 5 new customers
And there should be total sales of "$8,566.00"
And the average order value should be "$2,141.50"
@ui
Scenario: Changing channel in administration dashboard
Given 4 customers have fulfilled 4 orders placed for total of "$5,241.00" mostly "Onion" product
And 2 more customers have fulfilled 2 orders placed for total of "$459.00" mostly "Banana" product
And 2 more customers have placed 3 orders for total of "$1,259.00" mostly "Banana" product
When I open administration dashboard
And I choose "United States" channel
Then I should see 2 new orders
And I should see 8 new customers
And there should be total sales of "$459.00"
And the average order value should be "$229.50"
@ui
Scenario: Seeing recent orders in a specific channel
Given 3 customers have placed 4 orders for total of "$8,566.00" mostly "Onion" product
And 2 more customers have placed 2 orders for total of "$459.00" mostly "Banana" product
When I open administration dashboard for "Poland" channel
Then I should see 4 new orders in the list
@ui
Scenario: Seeing recent orders in a specific channel
Given 3 customers have placed 4 orders for total of "$8,566.00" mostly "Onion" product
And 2 more customers have placed 2 orders for total of "$459.00" mostly "Banana" product
When I open administration dashboard for "United States" channel
Then I should see 2 new orders in the list

View file

@ -0,0 +1,67 @@
@admin_dashboard
Feature: Statistics
In order to gain insight into my sales performance and customers activity
As an Administrator
I want to view comprehensive statistics
Background:
Given the store operates on a single channel in "United States"
And the store ships everywhere for Free
And the store allows paying Offline
And the store has a product "Sylius T-Shirt"
And this product has "Red XL" variant priced at "$40.00"
And I am logged in as an administrator
@ui @no-api
Scenario: Seeing statistics for the current year and default channel when expectations are not specified
Given it is "2022-12-31" now
And 2 new customers have fulfilled 2 orders placed for total of "$1,000.00"
And it is "2023-01-01" now
And 3 new customers have fulfilled 4 orders placed for total of "$2,000.21"
And it is "2023-02-01" now
And 2 more new customers have paid 2 orders placed for total of "$5,000.37"
When I view statistics
Then I should see 5 new customers
And I should see 6 new orders
And there should be total sales of "$7,000.58"
And the average order value should be "$1,166.76"
@ui @javascript
Scenario: Seeing statistics for the previous year
Given it is "2022-01-01" now
And 3 new customers have fulfilled 2 orders placed for total of "$2,000.00"
And it is "2023-02-01" now
And 4 more new customers have paid 5 orders placed for total of "$5,000.37"
And 2 more new customers have paid 2 orders placed for total of "$5,000.37"
When I view statistics for "United States" channel and previous year split by month
Then I should see 3 new customers
And I should see 2 new orders
And there should be total sales of "$2,000.00"
And the average order value should be "$1,000.00"
@ui @javascript
Scenario: Seeing statistics for the next year
Given it is "2022-01-01" now
And 3 new customers have fulfilled 2 orders placed for total of "$2,000.00"
And it is "2023-02-01" now
And 4 more new customers have paid 5 orders placed for total of "$5,000.37"
And 2 more new customers have paid 2 orders placed for total of "$5,000.37"
When I view statistics for "United States" channel and previous year split by month
And I view statistics for "United States" channel and next year split by month
Then I should see 6 new customers
And I should see 7 new orders
And there should be total sales of "$10,000.74"
And the average order value should be "$1,428.68"
@ui @javascript
Scenario: Seeing statistics that include only fulfilled orders that were not cancelled
Given 4 new customers have fulfilled 4 orders placed for total of "$5,241.00"
And 2 more new customers have placed 2 orders for total of "$459.00"
And 2 new customers have added products to the cart for total of "$3,450.00"
And a single customer has placed an order for total of "$1,000.00"
But the customer cancelled this order
When I view statistics for "United States" channel and current year split by month
Then I should see 4 new orders
And I should see 9 new customers
And there should be total sales of "$5,241.00"
And the average order value should be "$1,310.25"

View file

@ -0,0 +1,62 @@
@admin_dashboard
Feature: Statistics for a specific channel
In order to gain insight into my sales performance and customers activity in a specific channel
As an Administrator
I want to view comprehensive statistics
Background:
Given the store operates on a channel named "WEB-POLAND"
And there is product "Onion" available in this channel
And the store operates on another channel named "WEB-US"
And there is product "Banana" available in that channel
And the store ships everywhere for Free
And the store allows paying Offline
And I am logged in as an administrator
@ui @no-api
Scenario: Seeing basic statistics for the first channel by default
Given 3 new customers have fulfilled 4 orders placed for total of "$8,566.00" mostly "Onion" product
And 2 more new customers have fulfilled 2 orders placed for total of "$459.00" mostly "Banana" product
When I view statistics
Then I should see 4 new orders
And I should see 5 new customers
And there should be total sales of "$8,566.00"
And the average order value should be "$2,141.50"
@ui @javascript
Scenario: Switching to the channel with only fulfilled orders
Given 4 new customers have fulfilled 4 orders placed for total of "$5,241.00" mostly "Onion" product
And 2 more new customers have fulfilled 2 orders placed for total of "$459.00" mostly "Banana" product
And 2 more new customers have placed 3 orders for total of "$1,259.00" mostly "Banana" product
When I view statistics for "WEB-POLAND" channel and current year split by month
And I choose "WEB-US" channel
Then I should see 2 new orders
And I should see 8 new customers
And there should be total sales of "$459.00"
And the average order value should be "$229.50"
@ui @javascript
Scenario: Switching to the channel with both fulfilled and placed orders
Given 4 new customers have fulfilled 4 orders placed for total of "$5,241.00" mostly "Onion" product
And 2 more new customers have fulfilled 2 orders placed for total of "$459.00" mostly "Banana" product
And 2 more new customers have placed 3 orders for total of "$1,259.00" mostly "Banana" product
When I view statistics for "WEB-US" channel
And I choose "WEB-POLAND" channel
Then I should see 4 new orders
And I should see 8 new customers
And there should be total sales of "$5,241.00"
And the average order value should be "$1,310.25"
@ui
Scenario: Seeing recent orders in a specific channel
Given 3 new customers have placed 4 orders for total of "$8,566.00" mostly "Onion" product
And 2 more new customers have placed 2 orders for total of "$459.00" mostly "Banana" product
When I view statistics for "WEB-POLAND" channel
Then I should see 4 new orders in the list
@ui
Scenario: Seeing recent orders in a specific channel
Given 3 new customers have placed 4 orders for total of "$8,566.00" mostly "Onion" product
And 2 more new customers have placed 2 orders for total of "$459.00" mostly "Banana" product
When I view statistics for "WEB-US" channel
Then I should see 2 new orders in the list

View file

@ -172,20 +172,3 @@ Feature: Allowing access only for correctly logged in users
And the customer has product "Stark T-Shirt" in the cart
And the customer logged out
Then the visitor has no access to change product "Stark T-Shirt" quantity to 2 in the customer cart
@api
Scenario: Accessing to the customers cart by the admin
Given the customer logged in
And the customer has product "Stark T-Shirt" in the cart
And the customer logged out
And there is logged in the administrator
When the administrator try to see the summary of customer's cart
Then the administrator should see "Stark T-Shirt" product with quantity 1 in the customer cart
@api
Scenario: Accessing to the visitors cart by the admin
Given there is the visitor
And the visitor has product "Stark T-Shirt" in the cart
And there is logged in the administrator
When the administrator try to see the summary of customer's cart
Then the administrator should see "Stark T-Shirt" product with quantity 1 in the visitor cart

View file

@ -15,7 +15,7 @@ Feature: Shipments are in the state "ready" after checkout
And the customer chose "Free" shipping method with "Cash on Delivery" payment
And I am logged in as an administrator
@ui
@ui @api
Scenario: Checking shipment state of a placed order
When I view the summary of the order "#00000666"
Then it should have shipment in state "Ready"

View file

@ -15,7 +15,7 @@ Feature: Shipping an order
And the customer chose "Free" shipping method with "Cash on Delivery" payment
And I am logged in as an administrator
@ui
@ui @api
Scenario: Finalizing order's shipment
Given I view the summary of the order "#00000666"
When specify its tracking code as "#00044"
@ -23,13 +23,13 @@ Feature: Shipping an order
Then I should be notified that the order has been successfully shipped
And it should have shipment in state shipped
@ui
@ui @api
Scenario: Unable to finalize shipped order's shipment
Given this order has already been shipped
When I view the summary of the order "#00000666"
Then I should not be able to ship this order
@ui
@ui @api
Scenario: Checking the shipment state of a completed order
Given this order has already been shipped
When I browse orders

View file

@ -10,7 +10,7 @@ Feature: Being unable to see details of a cart
And the customer added "PHP T-Shirt" product to the cart
And I am logged in as an administrator
@ui
Scenario: Seeing basic information about an order
@ui @api
Scenario: Being unable to see the details of a cart
When I try to view the summary of the customer's latest cart
Then I should be informed that the order does not exist

View file

@ -13,20 +13,22 @@ Feature: Tracking changes on order addresses
And the customer bought a single "Italian suit"
When I am logged in as an administrator
@ui
@api @ui
Scenario: Browsing order's addresses history after changing it by customer
Given the customer "Barney Stinson" addressed it to "East 84st Street and 3rd Avenue", "10118" "New York" in the "United States" with identical billing address
And the customer changed shipping address' street to "211 Madison Avenue"
And the customer chose "Free" shipping method with "Cash on Delivery" payment
When I browse order's "#00000001" history
Then there should be 2 changes in the registry
Then there should be 2 shipping address changes in the registry
Then there should be 1 billing address changes in the registry
@ui
@api @ui
Scenario: Browsing order's addresses history after changing it by administrator
Given the customer "Barney Stinson" addressed it to "East 84st Street and 3rd Avenue", "10118" "New York" in the "United States" with identical billing address
And the customer chose "Free" shipping method with "Cash on Delivery" payment
When I want to modify a customer's shipping address of this order
And I specify their shipping address as "New York", "150 W. 85th Street", "10028", "United States" for "Ted Mosby"
When I want to modify a customer's billing address of this order
And I specify their billing address as "New York", "150 W. 85th Street", "10028", "United States" for "Ted Mosby"
And I save my changes
And I browse order's "#00000001" history
Then there should be 2 changes in the registry
Then there should be 1 shipping address changes in the registry
Then there should be 2 billing address changes in the registry

View file

@ -12,8 +12,8 @@ Feature: Accessing payment's order from the payment index
And there is an "#00000001" order with "Apple" product
And I am logged in as an administrator
@ui
Scenario: Accessing payment's order from the payments index
@ui @api
Scenario: Accessing payment's order from the payment
Given I am browsing payments
When I go to the details of the first payment's order
Then I should see order page with details of order "#00000001"
Then I should see the details of order "#00000001"

View file

@ -9,10 +9,16 @@ Feature: Adding associations to an existing product
And the store has "LG G3", "LG headphones" and "LG earphones" products
And I am logged in as an administrator
@ui @javascript
@ui @javascript @no-api
Scenario: Adding an association to an existing product
When I want to modify the "LG G3" product
And I associate as "Accessories" the "LG headphones" and "LG earphones" products
And I save my changes
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"
@api @no-ui
Scenario: Adding an association to na existing product
When I associate as "Accessories" the product "LG G3" with the products "LG headphones" and "LG earphones"
And this product should have an association "Accessories"
And this association should have products "LG headphones" and "LG earphones"

View file

@ -38,7 +38,7 @@ Feature: Adding a new product with associations
And this product should not have an association "Accessories" with product "LG earphones"
And 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
Given the store has 123 product association type
When I want to create a new simple product

View file

@ -9,7 +9,7 @@ Feature: Changing associations of an existing product
And the store has "LG G3", "LG headphones" and "LG earphones" products
And I am logged in as an administrator
@ui @javascript
@ui @javascript @no-api
Scenario: Changing associated products of a product association
Given the product "LG G3" has an association "Accessories" with product "LG headphones"
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
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
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
@ -27,3 +39,9 @@ Feature: Changing associations of an existing product
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 not have an association "Accessories" with product "LG earphones"
@api @no-ui
Scenario: Removing a product of a product association
Given the product "LG G3" has an association "Accessories" with products "LG headphones" and "LG earphones"
When I remove the product "LG earphones" from this product association
Then this association should only have product "LG headphones"

View file

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

View file

@ -65,7 +65,7 @@ Feature: Sorting listed products
Then I should see 3 products in the list
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
When I change my locale to "Polish"
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 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
When I change my locale to "Polish"
And I browse products

View file

@ -5,7 +5,8 @@ Feature: Sorting listed products from a taxon by position
I want to sort products from a taxon by their positions
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 "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
@ -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 I am logged in as an administrator
@ui
@ui @no-api
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
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
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
When I am browsing the 2nd page of products from "Soft Toys" taxon
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
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
When I am browsing the 1st page of products from "Soft Toys" taxon
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 this product should be at position 16
@ui
@ui @no-api
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
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 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
When I am browsing the 1st page of products from "Soft Toys" taxon
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
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
When I am browsing the 2nd page of products from "Soft Toys" taxon
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
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
Given I added a product "Big pug"
And I assigned this product to "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
When I am browsing products from "Soft Toys" taxon
And I set the position of "Young pug" to 0
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
Given I am browsing products from "Soft Toys" taxon
When I set the position of "Young pug" to "test"
And I save my new configuration
Then I should be notified that the position "test" is invalid
@ui
@ui @api
Scenario: Sort products in descending order
When I am browsing products from "Soft Toys" taxon
And I start sorting products by position
Then the first product on the list should have name "Ultimate Pug"
@ui
And I sort this taxon's products "descending" by "position"
Then the first product on the list within this taxon should have name "Ultimate Pug"
@ui @api
Scenario: Products are sorted by position in ascending order by default
When I am browsing products from "Soft Toys" taxon
Then the first product on the list should have name "Old pug"
Then the first product on the list within this taxon should have name "Old pug"

View file

@ -14,6 +14,6 @@ Feature: Accessing the price history from the configurable product show page
@ui @no-api
Scenario: Being able to access the price history of variant from the configurable product show page
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
Then I should see 1 log entries in the catalog price history

View file

@ -17,6 +17,6 @@ Feature: Accessing the price history from the simple product show page
@ui @no-api
Scenario: Being able to access price history from simple product show page
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
Then I should see 2 log entries in the catalog price history

View file

@ -12,14 +12,14 @@ Feature: Accessing to product edit page from show page
And I am logged in as an administrator
And I am browsing products
@ui
@ui @no-api
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
Then I should be on "Iron shield" product edit page
@ui
@ui @no-api
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
Then I should be on "Iron shield - very big" variant edit page

View file

@ -1,4 +1,4 @@
@viewing_products
@viewing_product_in_admin_panel
Feature: Checking products in the shop while viewing them
In order to check a product in shop in all channels it is available in
As an Administrator
@ -14,18 +14,18 @@ Feature: Checking products in the shop while viewing them
@ui @no-api
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
When I access "Bugatti" product page
When I access the "Bugatti" product
And I show this product in the "Europe" channel
Then I should see this product in the "Europe" channel in the shop
@ui @no-api
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
Then I should see this product in the "Europe" channel in the shop
@ui @no-api
Scenario: Being unable to access product show page in shop when the product is 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

View file

@ -1,4 +1,4 @@
@viewing_products
@viewing_product_in_admin_panel
Feature: Seeing applied catalog promotions details for a simple product
In order to be aware of simple product price change reason
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 is enabled
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
When I access "Ursus C-355" product
Then this product price should be decreased by catalog promotion "Company bankruptcy sale" in "United States" channel

View file

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

View file

@ -17,55 +17,69 @@ Feature: Viewing details of a product with variants
And I am logged in as an administrator
And I am browsing products
@ui
Scenario: Viewing a configurable product show page
When I access "Iron Shield" product page
@ui @no-api
Scenario: Viewing a configurable product
When I access the "Iron Shield" product
Then I should see product show page with variants
And I should see product name "Iron Shield"
@ui
Scenario: Viewing taxonomy block
@ui @api
Scenario: Viewing taxonomies
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" 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"
And I should see product taxon is "Shield"
And I should see product taxon "Shield"
@ui
Scenario: Viewing options block
When I access "Iron Shield" product page
@ui @api
Scenario: Viewing options
When I access the "Iron Shield" product
Then I should see option "Shield size"
@ui
Scenario: Viewing variants block
When I access "Iron Shield" product page
@ui @api
Scenario: Viewing variants
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
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
@ui @javascript @api
Scenario: Viewing media block
Scenario: Viewing media
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
@ui
Scenario: Viewing "more details" block
@ui @api
Scenario: Viewing more details
Given the product "Iron Shield" has the slug "iron-shield"
And the description of product "Iron Shield" is "Shield created by dwarf"
And the meta keywords of product "Iron Shield" is "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"
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 meta keywords is "shield"
And I should see product's short description is "good shield"
@ui
Scenario: Viewing associations block
@ui @api
Scenario: Viewing association types
Given the store has a "Glass shield" product
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"

View file

@ -11,54 +11,54 @@ Feature: Viewing details of a simple product
And I am logged in as an administrator
And I am browsing products
@ui
@ui @no-api
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
And I should see product name "Iron Shield"
And I should see product breadcrumb "Iron Shield"
@ui
@ui @no-api
Scenario: Viewing pricing block
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"
And I should see original price "$25.00" for channel "United States"
@ui
@ui @no-api
Scenario: Viewing price block without channel enable
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"
And I should see the product in neither channel
And I should not see price for channel "United States"
@ui
@ui @no-api
Scenario: Viewing details block
Given the store has a tax category "No tax" with a code "nt"
And product's "Iron Shield" code is "123456789"
And there are 4 units of product "Iron Shield" available in the inventory
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"
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 product's tax category is "No tax"
@ui
@ui @no-api
Scenario: Viewing taxonomy block
Given this product belongs to "Shield"
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"
And I should see product taxon is "Shield"
And I should see product taxon "Shield"
@ui
@ui @no-api
Scenario: Viewing shipping block
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 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"
And I should see product's height is 10
And I should see product's width is 15
@ -68,26 +68,26 @@ Feature: Viewing details of a simple product
@ui @javascript @api
Scenario: Viewing media block
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
@ui
@ui @no-api
Scenario: Viewing "more details" block
Given the product "Iron Shield" has the slug "iron-shield"
And the description of product "Iron Shield" is "Shield created by dwarf"
And the meta keywords of product "Iron Shield" is "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"
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 meta keywords is "shield"
And I should see product's short description is "good shield"
@ui
@ui @no-api
Scenario: Viewing associations block
Given the store has "Similar" and "Dwarf equipment" product association types
And the store has a "Glass Shield" product
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"

View file

@ -1,4 +1,4 @@
@viewing_products
@viewing_product_in_admin_panel
Feature: Seeing the lowest price before the discount for a simple product
In order to be aware of simple product prices
As an Administrator
@ -13,10 +13,10 @@ Feature: Seeing the lowest price before the discount for a simple product
@ui @no-api
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"
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
@ui @no-api
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

View file

@ -1,4 +1,4 @@
@viewing_products
@viewing_product_in_admin_panel
Feature: Seeing the lowest price before the discount within variant
In order to be aware of variant's prices
As an Administrator
@ -15,6 +15,6 @@ Feature: Seeing the lowest price before the discount within variant
@ui @no-api
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
And I should see the lowest price before the discount of "$11.00" for "Wyborowa Vodka Potato" variant in "United States" channel

View file

@ -1,4 +1,4 @@
@viewing_products
@viewing_product_in_admin_panel
Feature: Viewing product's non translatable attributes
In order to see product's non translatable attribute
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 browsing products
@ui
@ui @api
Scenario: Viewing product's non translatable attributes along with default ones
When I access "Iron Pickaxe" product page
Then I should see non-translatable attribute "crit chance" with value "10 %"
When I access the "Iron Pickaxe" product
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 "Żelazo" in "Polish (Poland)" locale

View file

@ -1,4 +1,4 @@
@viewing_products
@viewing_product_in_admin_panel
Feature: Viewing product's attributes in different locales
In order to see product's specification in all locales
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 browsing products
@ui
@ui @api
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
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

View file

@ -14,7 +14,6 @@ Feature: Not seeing the lowest price for a product that has a taxon excluded on
And the store also has a product "Cauliflower" priced at "$25.00"
And it belongs to "Vegetables" and "Special offers"
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
Scenario: Not seeing the lowest price for a product that has a taxon excluded on the channel

View file

@ -17,24 +17,24 @@ Feature: Viewing product's select attributes
Then I should see the product attribute "T-Shirt material" with value "Banana skin" on the list
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
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
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
@ui @javascript
@ui @javascript @api @no-postgres
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"
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
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
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
Then I should see the product attribute "T-Shirt material" with value "Banana skin"

View file

@ -15,8 +15,8 @@ Feature: Accessing shipment's order from the shipments index
And the customer chose "UPS" shipping method with "Cash on Delivery" payment
And I am logged in as an administrator
@ui
Scenario: Accessing shipment's order from the shipments index
@ui @api
Scenario: Accessing shipment's order from the shipment
When I browse shipments
And I move to the details of first shipment's order
Then I should see order page with details of order "00000001"
Then I should see the details of order "#00000001"

View file

@ -10,7 +10,7 @@ Feature: Adding a new shipping method with rule
And the store has a zone "United States" with code "US"
And I am logged in as an administrator
@ui @javascript
@ui @javascript @api
Scenario: Adding a new shipping method with total weight greater than or equal rule
When I want to create a new shipping method
And I specify its code as "FED_EX_CARRIER"
@ -24,7 +24,7 @@ Feature: Adding a new shipping method with rule
Then I should be notified that it has been successfully created
And the shipping method "FedEx Carrier" should appear in the registry
@ui @javascript
@ui @javascript @api
Scenario: Adding a new shipping method with total weight less than or equal rule
When I want to create a new shipping method
And I specify its code as "FED_EX_CARRIER"
@ -38,7 +38,7 @@ Feature: Adding a new shipping method with rule
Then I should be notified that it has been successfully created
And the shipping method "FedEx Carrier" should appear in the registry
@ui @javascript
@ui @javascript @api
Scenario: Adding a new shipping method with order total greater than or equal rule
When I want to create a new shipping method
And I specify its code as "FED_EX_CARRIER"
@ -52,7 +52,7 @@ Feature: Adding a new shipping method with rule
Then I should be notified that it has been successfully created
And the shipping method "FedEx Carrier" should appear in the registry
@ui @javascript
@ui @javascript @api
Scenario: Adding a new shipping method with order total less than or equal rule
When I want to create a new shipping method
And I specify its code as "FED_EX_CARRIER"

View file

@ -10,14 +10,6 @@ Feature: Editing shipping method
And the store allows shipping with "UPS Carrier" identified by "UPS_CARRIER"
And I am logged in as an administrator
@todo
Scenario: Trying to change shipping method code
When I want to modify a shipping method "UPS Carrier"
And I change its code to "UPS"
And I save my changes
Then I should be notified that code cannot be changed
And shipping method "UPS Carrier" should still have code "UPS_CARRIER"
@ui @api
Scenario: Seeing disabled code field when editing shipping method
When I want to modify a shipping method "UPS Carrier"

View file

@ -10,7 +10,7 @@ Feature: Shipping method code validation
And the store has a zone "United States" with code "US"
And I am logged in as an administrator
@ui
@ui @api
Scenario: Trying to add a new shipping method with special symbols in the code
When I want to create a new shipping method
And I name it "FedEx Carrier" in "English (United States)"
@ -19,7 +19,7 @@ Feature: Shipping method code validation
Then I should be notified that code needs to contain only specific symbols
And shipping method with name "FedEx Carrier" should not be added
@ui
@ui @api
Scenario: Trying to add a new shipping method with spaces in the code
When I want to create a new shipping method
And I name it "FedEx Carrier" in "English (United States)"
@ -27,14 +27,3 @@ Feature: Shipping method code validation
And I try to add it
Then I should be notified that code needs to contain only specific symbols
And shipping method with name "FedEx Carrier" should not be added
@ui @javascript
Scenario: Trying to add a new shipping method with
When I want to create a new shipping method
And I name it "FedEx Carrier First US Division" in "English (United States)"
And I specify its code as "PEC-US_01"
And I define it for the zone named "United States"
And I choose "Flat rate per shipment" calculator
And I specify its amount as 50 for "Web-US" channel
And I add it
Then the shipping method "FedEx Carrier First US Division" should appear in the registry

View file

@ -10,7 +10,7 @@ Feature: Shipping method flat rate per shipment calculator validation
And the store has a zone "United States" with code "US"
And I am logged in as an administrator
@ui @javascript
@ui @javascript @api
Scenario: Trying to add a new shipping method with flat rate per shipment calculator without specifying its amount
When I want to create a new shipping method
And I specify its code as "FED_EX_CARRIER"
@ -21,7 +21,7 @@ Feature: Shipping method flat rate per shipment calculator validation
Then I should be notified that amount for "Web" channel should not be blank
And shipping method with name "FedEx Carrier" should not be added
@ui @javascript
@ui @javascript @api
Scenario: Trying to add a new shipping method with flat rate per shipment calculator with charge below 0
When I want to create a new shipping method
And I specify its code as "FED_EX_CARRIER"

View file

@ -10,7 +10,7 @@ Feature: Shipping method flat rate per unit calculator validation
And the store has a zone "United States" with code "US"
And I am logged in as an administrator
@ui @javascript
@ui @javascript @api
Scenario: Trying to add a new shipping method with flat rate per unit calculator without specifying its amount
When I want to create a new shipping method
And I specify its code as "FED_EX_CARRIER"
@ -21,7 +21,7 @@ Feature: Shipping method flat rate per unit calculator validation
Then I should be notified that amount for "Web" channel should not be blank
And shipping method with name "FedEx Carrier" should not be added
@ui @javascript
@ui @javascript @api
Scenario: Trying to add a new shipping method with flat rate per unit calculator with charge below 0
When I want to create a new shipping method
And I specify its code as "FED_EX_CARRIER"

View file

@ -5,7 +5,8 @@ Feature: Shipping method validation
I want to be prevented from adding it without specifying required fields
Background:
Given the store operates on a single channel in "United States"
Given the store operates on a channel named "Web-US" in "USD" currency
And the store has a zone "United States" with code "US"
And the store is available in "English (United States)"
And I am logged in as an administrator
@ -46,10 +47,38 @@ Feature: Shipping method validation
Then I should be notified that name is required
And this shipping method should still be named "UPS Ground"
@ui
@ui @api
Scenario: Trying to remove zone from existing shipping method
Given the store allows shipping with "UPS Ground"
When I want to modify this shipping method
And I remove its zone
And I try to save my changes
Then I should be notified that zone has to be selected
Then I should be notified that the zone is required
@ui @javascript @api
Scenario: Adding a new shipping method with order total greater than or equal rule that contains invalid data
When I want to create a new shipping method
And I specify its code as "FED_EX_CARRIER"
And I specify its position as 0
And I name it "FedEx Carrier" in "English (United States)"
And I define it for the zone named "United States"
And I choose "Flat rate per shipment" calculator
And I specify its amount as 50 for "Web-US" channel
And I add the "Total weight greater than or equal" rule configured with invalid data
And I add it
Then I should be notified that the weight rule has an invalid configuration
And the shipping method "FedEx Carrier" should not appear in the registry
@ui @javascript @api
Scenario: Adding a new shipping method with order total less than or equal rule that contains invalid data
When I want to create a new shipping method
And I specify its code as "FED_EX_CARRIER"
And I specify its position as 0
And I name it "FedEx Carrier" in "English (United States)"
And I define it for the zone named "United States"
And I choose "Flat rate per shipment" calculator
And I specify its amount as 50 for "Web-US" channel
And I add the "Items total less than or equal" rule configured with invalid data for "Web-US" channel
And I add it
Then I should be notified that the amount rule has an invalid configuration in "Web-US" channel
And the shipping method "FedEx Carrier" should not appear in the registry

View file

@ -1581,14 +1581,14 @@ parameters:
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
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
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\\.$#"

View file

@ -132,18 +132,50 @@ final class ResponseChecker implements ResponseCheckerInterface
return false;
}
/** @param string|int $value */
public function hasSubResourceWithValue(Response $response, string $subResource, string $key, $value): bool
{
foreach ($this->getResponseContentValue($response, $subResource) as $resource) {
if ($resource[$key] === $value) {
return true;
public function hasValuesInAnySubresourceObjectCollection(
Response $response,
string $subResource,
array $expectedValues,
): bool {
$resourceCollection = $this->getResponseContentValue($response, $subResource);
$this->assertIsArray($resourceCollection);
foreach ($resourceCollection as $resource) {
$this->assertIsArray($resource);
foreach ($expectedValues as $key => $expectedValue) {
if (!array_key_exists($key, $resource) || $resource[$key] !== $expectedValue) {
continue 2;
}
}
return true;
}
return false;
}
public function hasValuesInSubresourceObject(
Response $response,
string $subResource,
array $expectedValues,
): bool {
$resource = $this->getResponseContentValue($response, $subResource);
$this->assertIsArray($resource);
$this->assertAllExpectedKeysArePresent($expectedValues, $resource);
foreach ($expectedValues as $key => $expectedValue) {
if ($resource[$key] !== $expectedValue) {
return false;
}
}
return true;
}
/** @param string|array $value */
public function hasItemOnPositionWithValue(Response $response, int $position, string $key, $value): bool
{
@ -234,9 +266,10 @@ final class ResponseChecker implements ResponseCheckerInterface
Assert::keyExists(
$content,
$key,
SprintfResponseEscaper::provideMessageWithEscapedResponseContent(
'Expected \'' . $key . '\' not found.',
$response,
sprintf(
'Expected to get: "%s" key in response, got keys: [%s]',
$key,
implode(', ', array_keys($content)),
),
);
@ -253,4 +286,26 @@ final class ResponseChecker implements ResponseCheckerInterface
return true;
}
private function assertIsArray(mixed $resource): void
{
Assert::isArray($resource, sprintf('Expected to get an array, got "%s"', gettype($resource)));
}
/**
* @param array<string, int|string> $expectedValues
* @param array<string, int|string> $resource
*/
private function assertAllExpectedKeysArePresent(array $expectedValues, array $resource): void
{
Assert::count(
array_diff_key($expectedValues, $resource),
0,
sprintf(
'Expected values array has keys: [%s], that are not present in the responses keys: [%s]',
implode(', ', array_keys(array_diff_key($expectedValues, $resource))),
implode(', ', array_keys($resource)),
),
);
}
}

View file

@ -51,7 +51,19 @@ interface ResponseCheckerInterface
public function hasItemWithValue(Response $response, string $key, int|string $value): bool;
public function hasSubResourceWithValue(Response $response, string $subResource, string $key, int|string $value): bool;
/** @param array<string, int|string> $expectedValues */
public function hasValuesInAnySubresourceObjectCollection(
Response $response,
string $subResource,
array $expectedValues,
): bool;
/** @param array<string, int|string> $expectedValues */
public function hasValuesInSubresourceObject(
Response $response,
string $subResource,
array $expectedValues,
): bool;
public function hasItemOnPositionWithValue(Response $response, int $position, string $key, array|string $value): bool;

View file

@ -19,6 +19,9 @@ use Sylius\Behat\Client\ApiClientInterface;
use Sylius\Behat\Client\ResponseCheckerInterface;
use Sylius\Behat\Context\Api\Resources;
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;
final class BrowsingCatalogPromotionProductVariantsContext implements Context
@ -40,6 +43,17 @@ final class BrowsingCatalogPromotionProductVariantsContext implements Context
$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$/
*/
@ -66,4 +80,71 @@ final class BrowsingCatalogPromotionProductVariantsContext implements Context
));
}
}
/**
* @Then :variant variant price should be decreased by catalog promotion :catalogPromotion in :channel channel
*/
public function variantPriceShouldBeDecreasedByCatalogPromotion(
ProductVariantInterface $variant,
CatalogPromotionInterface $catalogPromotion,
ChannelInterface $channel,
): void {
Assert::true(
$this->variantHasCatalogPromotionInChannel($variant, $catalogPromotion, $channel),
sprintf(
'Catalog promotion "%s" was not found in applied promotions of variant "%s" in channel "%s".',
$catalogPromotion->getCode(),
$variant->getCode(),
$channel->getCode(),
),
);
}
/**
* @Then :variant variant price should not be decreased by catalog promotion :catalogPromotion in :channel channel
*/
public function variantPriceShouldNotBeDecreasedByCatalogPromotion(
ProductVariantInterface $variant,
CatalogPromotionInterface $catalogPromotion,
ChannelInterface $channel,
): void {
Assert::false(
$this->variantHasCatalogPromotionInChannel($variant, $catalogPromotion, $channel),
sprintf(
'Catalog promotion "%s" was found in applied promotions of variant "%s" in channel "%s".',
$catalogPromotion->getCode(),
$variant->getCode(),
$channel->getCode(),
),
);
}
private function variantHasCatalogPromotionInChannel(
ProductVariantInterface $variant,
CatalogPromotionInterface $catalogPromotion,
ChannelInterface $channel
): bool {
$variantData = $this->getDataOfVariantWithCode($variant->getCode());
$promotions = $variantData['channelPricings'][$channel->getCode()]['appliedPromotions'] ?? [];
foreach ($promotions as $promotion) {
if ($promotion['code'] === $catalogPromotion->getCode()) {
return true;
}
}
return false;
}
private function getDataOfVariantWithCode(string $code): array
{
$variantsData = $this->responseChecker->getCollection($this->client->getLastResponse());
foreach ($variantsData as $variantData) {
if ($variantData['code'] === $code) {
return $variantData;
}
}
throw new \InvalidArgumentException(sprintf('Variant with code "%s" was not found.', $code));
}
}

View file

@ -16,8 +16,11 @@ namespace Sylius\Behat\Context\Api\Admin;
use ApiPlatform\Api\IriConverterInterface;
use Behat\Behat\Context\Context;
use Sylius\Behat\Client\ApiClientInterface;
use Sylius\Behat\Client\RequestFactoryInterface;
use Sylius\Behat\Client\ResponseCheckerInterface;
use Sylius\Behat\Context\Api\Resources;
use Sylius\Behat\Context\Api\Subresources;
use Sylius\Behat\Service\Converter\SectionAwareIriConverterInterface;
use Sylius\Behat\Service\SecurityServiceInterface;
use Sylius\Behat\Service\SharedSecurityServiceInterface;
use Sylius\Behat\Service\SharedStorageInterface;
@ -27,6 +30,7 @@ use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\PaymentMethodInterface;
use Sylius\Component\Core\Model\ShipmentInterface;
use Sylius\Component\Core\Model\ShippingMethodInterface;
use Sylius\Component\Currency\Model\CurrencyInterface;
use Sylius\Component\Order\OrderTransitions;
@ -41,10 +45,12 @@ final class ManagingOrdersContext implements Context
public function __construct(
private ApiClientInterface $client,
private ResponseCheckerInterface $responseChecker,
private RequestFactoryInterface $requestFactory,
private IriConverterInterface $iriConverter,
private SecurityServiceInterface $adminSecurityService,
private SharedStorageInterface $sharedStorage,
private SharedSecurityServiceInterface $sharedSecurityService,
private SectionAwareIriConverterInterface $sectionAwareIriConverter,
) {
}
@ -55,7 +61,7 @@ final class ManagingOrdersContext implements Context
public function iSeeTheOrder(OrderInterface $order): void
{
$response = $this->client->show(Resources::ORDERS, $order->getTokenValue());
Assert::same($this->responseChecker->getValue($response, '@id'), $this->iriConverter->getIriFromResource($order));
Assert::same($this->responseChecker->getValue($response, '@id'), $this->sectionAwareIriConverter->getIriFromResourceInSection($order, 'admin'));
$this->sharedStorage->set('order', $order);
}
@ -69,6 +75,14 @@ final class ManagingOrdersContext implements Context
$this->client->index(Resources::ORDERS);
}
/**
* @When I browse order's :order history
*/
public function iBrowseOrderHistory(OrderInterface $order): void
{
$this->iSeeTheOrder($order);
}
/**
* @When I filter
*/
@ -93,6 +107,30 @@ final class ManagingOrdersContext implements Context
$this->client->addFilter('checkoutCompletedAt[after]', $dateTime);
}
/**
* @When specify its tracking code as :trackingCode
*/
public function specifyItsTrackingCodeAs(string $trackingCode): void
{
$shipment = $this->sharedStorage->get('order')->getShipments()->first();
$this->client->buildUpdateRequest(
Resources::SHIPMENTS,
(string) $shipment->getId(),
);
$this->client->addRequestData('tracking', $trackingCode);
$this->client->update();
}
/**
* @When /^I try to view the summary of the (customer's latest cart)$/
*/
public function iTryToViewTheSummaryOfTheCustomersLatestCart(OrderInterface $cart): void
{
$this->client->show(Resources::ORDERS, $cart->getTokenValue());
}
/**
* @When I specify filter date to as :dateTime
*/
@ -978,6 +1016,105 @@ final class ManagingOrdersContext implements Context
Assert::same($this->getTotalAsInt($subTotal), $orderItem['unitPrice'] * $orderItem['quantity'] + $unitPromotionAdjustments);
}
/**
* @Then I should be notified that the order has been successfully shipped
*/
public function iShouldBeNotifiedThatTheOrderHasBeenSuccessfullyShipped(): void
{
$response = $this->client->getLastResponse();
Assert::true(
$this->responseChecker->isUpdateSuccessful($response),
'Order could not be shipped. Reason: ' . $response->getContent(),
);
}
/**
* @Then it should have shipment in state shipped
*/
public function itShouldHaveShipmentInStateShipped(): void
{
$shipmentIri = $this->responseChecker->getValue(
$this->client->show(Resources::ORDERS, $this->sharedStorage->get('order')->getTokenValue()),
'shipments',
)[0];
Assert::true(
$this->responseChecker->hasValue($this->client->showByIri($shipmentIri['@id']), 'state', ShipmentInterface::STATE_SHIPPED),
sprintf('Shipment for this order is not %s', ShipmentInterface::STATE_SHIPPED),
);
}
/**
* @Then this order should have order shipping state :orderShippingState
*/
public function thisOrderShouldHaveOrderShippingState(string $orderShippingState): void
{
$ordersResponse = $this->client->index(Resources::ORDERS);
Assert::true(
$this->responseChecker->hasItemWithValue($ordersResponse, 'shippingState', strtolower($orderShippingState)),
sprintf('Order does not have %s shipping state', $orderShippingState),
);
}
/**
* @Then I should not be able to ship this order
*/
public function iShouldNotBeAbleToShipThisOrder(): void
{
$order = $this->sharedStorage->get('order');
$this->client->applyTransition(
Resources::SHIPMENTS,
(string) $order->getShipments()->first()->getId(),
ShipmentTransitions::TRANSITION_SHIP,
);
Assert::false(
$this->responseChecker->isUpdateSuccessful($this->client->getLastResponse()),
'Order has been shipped, but should not.',
);
}
/**
* @Then I should be informed that the order does not exist
*/
public function iShouldBeInformedThatTheOrderDoesNotExist(): void
{
Assert::same(
$this->responseChecker->getError($this->client->getLastResponse()),
'Not Found',
);
}
/**
* @Then there should be :count shipping address changes in the registry
*/
public function thereShouldBeCountShippingAddressChangesInTheRegistry(int $count): void
{
$order = $this->sharedStorage->get('order');
$response = $this->client->subResourceIndex(
Resources::ADDRESSES,
Subresources::ADDRESSES_LOG_ENTRIES,
(string) $order->getShippingAddress()->getId(),
);
Assert::same($this->responseChecker->countCollectionItems($response), $count);
}
/**
* @Then there should be :count billing address changes in the registry
*/
public function thereShouldBeCountBillingAddressChangesInTheRegistry(int $count): void
{
$order = $this->sharedStorage->get('order');
$response = $this->client->subResourceIndex(
Resources::ADDRESSES,
Subresources::ADDRESSES_LOG_ENTRIES,
(string) $order->getBillingAddress()->getId(),
);
Assert::same($this->responseChecker->countCollectionItems($response), $count);
}
/**
* @param array<string, mixed> $address
*/

View file

@ -13,6 +13,7 @@ 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;
@ -23,6 +24,7 @@ use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Payment\PaymentTransitions;
use Symfony\Component\HttpFoundation\Request as HttpRequest;
use Webmozart\Assert\Assert;
final class ManagingPaymentsContext implements Context
@ -31,6 +33,7 @@ final class ManagingPaymentsContext implements Context
private ApiClientInterface $client,
private ResponseCheckerInterface $responseChecker,
private SectionAwareIriConverterInterface $sectionAwareIriConverter,
private IriConverterInterface $iriConverter,
private string $apiUrlPrefix,
) {
}
@ -44,6 +47,34 @@ final class ManagingPaymentsContext implements Context
$this->client->index(Resources::PAYMENTS);
}
/**
* @When I go to the details of the first payment's order
*/
public function iGoToTheDetailsOfTheFirstPaymentSOrder(): void
{
$firstPayment = $this->responseChecker->getCollection($this->client->getLastResponse())[0];
/** @var OrderInterface $order */
$order = $this->iriConverter->getResourceFromIri($firstPayment['order']);
$this->client->customItemAction(Resources::ORDERS, $order->getTokenValue(), HttpRequest::METHOD_GET, 'payments');
}
/**
* @Then I should see the details of order :order
*/
public function iShouldSeeOrderWithDetails(OrderInterface $order): void
{
Assert::true(
$this->responseChecker->hasItemWithValue(
$this->client->getLastResponse(),
'order',
$this->sectionAwareIriConverter->getIriFromResourceInSection($order, 'admin'),
),
sprintf('Order with number %s does not exist', $order->getNumber()),
);
}
/**
* @When I complete the payment of order :order
*/

View file

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

View file

@ -131,12 +131,14 @@ final class ManagingProductImagesContext implements Context
*/
public function theProductShouldHaveAnImageWithType(ProductInterface $product, string $type): void
{
Assert::true($this->responseChecker->hasSubResourceWithValue(
$this->client->show(Resources::PRODUCTS, $product->getCode()),
'images',
'type',
$type,
));
Assert::true(
$this->responseChecker->hasValuesInAnySubresourceObjectCollection(
$this->client->show(Resources::PRODUCTS, $product->getCode()),
'images',
['type' => $type],
),
sprintf('Product %s does not have an image with %s type', $product->getName(), $type),
);
}
/**
@ -162,12 +164,14 @@ final class ManagingProductImagesContext implements Context
*/
public function thisProductShouldNotHaveAnyImagesWithType(ProductInterface $product, string $type): void
{
Assert::false($this->responseChecker->hasSubResourceWithValue(
$this->client->show(Resources::PRODUCTS, $product->getCode()),
'images',
'type',
$type,
));
Assert::false(
$this->responseChecker->hasValuesInAnySubresourceObjectCollection(
$this->client->show(Resources::PRODUCTS, $product->getCode()),
'images',
['type' => $type],
),
sprintf('Product %s does not have an image with %s type', $product->getName(), $type),
);
}
/**

View file

@ -16,7 +16,9 @@ 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\SharedStorageInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductTaxonInterface;
use Sylius\Component\Core\Model\TaxonInterface;
@ -24,8 +26,47 @@ use Webmozart\Assert\Assert;
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());
}
/**
* @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
*/
@ -105,4 +178,15 @@ final class ManagingProductTaxonsContext implements Context
'Product taxons cannot be duplicated.',
);
}
/**
* @Then I should be notified that the position :position is invalid
*/
public function iShouldBeNotifiedThatThePositionIsInvalid(): void
{
Assert::contains(
(string) $this->responseChecker->getError($this->client->getLastResponse()),
'The type of the "position" attribute must be "int", "string" given.',
);
}
}

View file

@ -24,7 +24,12 @@ use Sylius\Component\Attribute\Model\AttributeValueInterface;
use Sylius\Component\Core\Model\AdminUserInterface;
use Sylius\Component\Core\Model\ChannelInterface;
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\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\ProductOptionInterface;
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());
}
/**
* @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
*/
@ -411,6 +424,125 @@ final class ManagingProductsContext implements Context
// 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
*/
@ -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
*/
public function iShouldSeeProductsInTheList(int $count): void
public function iShouldSeeProductsInTheList(int $count = 1): void
{
Assert::count($this->responseChecker->getCollection($this->client->getLastResponse()), $count);
}
@ -562,7 +695,7 @@ final class ManagingProductsContext implements Context
$this->responseChecker->hasItemWithValues($this->client->getLastResponse(), [
'product' => $this->sectionAwareIriConverter->getIriFromResourceInSection($product, 'admin'),
'taxon' => $this->sectionAwareIriConverter->getIriFromResourceInSection($taxon, 'admin'),
])
]),
);
}
@ -612,11 +745,31 @@ final class ManagingProductsContext implements Context
*/
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'));
}
/**
* @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
{
/** @var AdminUserInterface $adminUser */
@ -830,14 +994,14 @@ final class ManagingProductsContext implements Context
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') {
return $product['code'];
}
if ($field === 'name') {
return $product['translations'][$this->getAdminLocaleCode()]['name'];
return $product['translations'][$this->getAdminLocaleCode()]['name'] ?? null;
}
return null;
@ -934,4 +1098,12 @@ final class ManagingProductsContext implements Context
Assert::same((string) $value, $expectedValue);
}
private function assertResponseHasTranslationFieldWithValue(string $field, string $value): void
{
Assert::same(
$this->responseChecker->getTranslationValue($this->client->getLastResponse(), $field),
$value,
);
}
}

View file

@ -836,7 +836,7 @@ final class ManagingPromotionsContext implements Context
$returnedPromotion = current($this->responseChecker->getCollectionItemsWithValue(
$this->client->getLastResponse(),
'code',
$promotion->getCode()
$promotion->getCode(),
));
Assert::same(

View file

@ -59,6 +59,19 @@ final class ManagingShipmentsContext implements Context
$this->client->addFilter('state', $state);
}
/**
* @When I move to the details of first shipment's order
*/
public function iMoveToDetailsOfFirstShipment(): void
{
$firstShipment = $this->responseChecker->getCollection($this->client->getLastResponse())[0];
/** @var OrderInterface $order */
$order = $this->iriConverter->getResourceFromIri($firstShipment['order']);
$this->client->customItemAction(Resources::ORDERS, $order->getTokenValue(), HttpRequest::METHOD_GET, 'shipments');
}
/**
* @When I choose :channel as a channel filter
*/
@ -290,6 +303,21 @@ final class ManagingShipmentsContext implements Context
Assert::same($productUnitsCounter, $amount);
}
/**
* @Then I should see the details of order :order
*/
public function iShouldSeeOrderWithDetails(OrderInterface $order): void
{
Assert::true(
$this->responseChecker->hasItemWithValue(
$this->client->getLastResponse(),
'order',
$this->sectionAwareIriConverter->getIriFromResourceInSection($order, 'admin'),
),
sprintf('Order with number %s does not exist', $order->getNumber()),
);
}
private function isShipmentForOrder(OrderInterface $order): bool
{
return $this->responseChecker->hasItemWithValue(

View file

@ -21,9 +21,11 @@ use Sylius\Behat\Context\Api\Resources;
use Sylius\Behat\Service\Converter\SectionAwareIriConverterInterface;
use Sylius\Behat\Service\SharedStorageInterface;
use Sylius\Component\Addressing\Model\ZoneInterface;
use Sylius\Component\Core\Formatter\StringInflector;
use Sylius\Component\Core\Model\AdminUserInterface;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\ShippingMethodInterface;
use Sylius\Component\Shipping\Calculator\DefaultCalculators;
use Symfony\Component\HttpFoundation\Request as HttpRequest;
use Webmozart\Assert\Assert;
@ -63,6 +65,82 @@ final class ManagingShippingMethodsContext implements Context
]);
}
/**
* @When I add the :rule rule configured with :weight
*/
public function iAddTheRuleConfiguredWithWeight(string $rule, int $weight): void
{
$type = StringInflector::nameToLowercaseCode($rule);
$this->client->addRequestData('rules', [[
'type' => $type,
'configuration' => [
'weight' => $weight,
],
]]);
}
/**
* @When I add the "Total weight greater than or equal" rule configured with invalid data
*/
public function iAddTheTotalWeightGreaterThanOrEqualRuleConfiguredWithInvalidData(): void
{
$this->client->addRequestData('rules', [
[
'type' => 'total_weight_greater_than_or_equal',
'configuration' => [
'weight' => true,
],
],
]);
}
/**
* @When /^I add the "([^"]+)" rule configured with (?:€|£|\$)([^"]+) for ("[^"]+" channel)$/
*/
public function iAddTheRuleConfiguredWithForChannel(string $rule, int $value, ChannelInterface $channel): void
{
match ($rule) {
'Items total less than or equal' => $this->client->addRequestData('rules', [
[
'type' => 'order_total_less_than_or_equal',
'configuration' => [
$channel->getCode() => [
'amount' => $value,
],
],
],
]),
'Items total greater than or equal' => $this->client->addRequestData('rules', [
[
'type' => 'order_total_greater_than_or_equal',
'configuration' => [
$channel->getCode() => [
'amount' => $value,
],
],
],
]),
default => throw new \InvalidArgumentException('Unsupported shipping method rule'),
};
}
/**
* @When /^I add the "Items total less than or equal" rule configured with invalid data for ("[^"]+" channel)$/
*/
public function iAddTheItemsTotalLessThanOrEqualRuleConfiguredWithInvalidData(ChannelInterface $channel): void
{
$this->client->addRequestData('rules', [
[
'type' => 'order_total_greater_than_or_equal',
'configuration' => [
$channel->getCode() => [
'amount' => true,
],
],
],
]);
}
/**
* @When I change my locale to :localeCode
*/
@ -123,6 +201,29 @@ final class ManagingShippingMethodsContext implements Context
]);
}
/**
* @When I do not specify amount for :calculatorName calculator
*/
public function iDoNotSpecifyAmountForCalculator(string $calculatorName): void
{
match ($calculatorName) {
'Flat rate per shipment' => $this->client->addRequestData('calculator', DefaultCalculators::FLAT_RATE),
'Flat rate per unit' => $this->client->addRequestData('calculator', DefaultCalculators::PER_UNIT_RATE),
'default' => throw new \InvalidArgumentException('Unsupported calculator name'),
};
$channelCode = $this->sharedStorage->get('channel')->getCode();
$this->client->addRequestData('configuration', [$channelCode => ['amount' => null]]);
}
/**
* @When I remove its zone
*/
public function iRemoveItsZone(): void
{
$this->client->replaceRequestData('zone', null);
}
/**
* @When I try to show :shippingMethod shipping method
*/
@ -310,6 +411,17 @@ final class ManagingShippingMethodsContext implements Context
);
}
/**
* @Then the shipping method :name should not appear in the registry
*/
public function theShippingMethodShouldNotAppearInTheRegistry(string $name): void
{
Assert::false(
$this->responseChecker->hasItemWithTranslation($this->client->index(Resources::SHIPPING_METHODS), 'en_US', 'name', $name),
sprintf('Shipping method with name %s exists', $name),
);
}
/**
* @Then /^(this shipping method) should still be in the registry$/
*/
@ -527,6 +639,17 @@ final class ManagingShippingMethodsContext implements Context
);
}
/**
* @Then I should be notified that the zone is required
*/
public function iShouldBeNotifiedThatZoneHasToBeIriAndCannotBeNull(): void
{
Assert::contains(
$this->responseChecker->getError($this->client->getLastResponse()),
'Expected IRI or nested document for attribute "zone", "NULL" given.',
);
}
/**
* @Then shipping method with :element :value should not be added
*/
@ -578,6 +701,72 @@ final class ManagingShippingMethodsContext implements Context
// Intentionally left blank
}
/**
* @Then shipping method :shippingMethod should still have code :code
*/
public function shippingMethodShouldStillHaveCode(ShippingMethodInterface $shippingMethod, string $code): void
{
Assert::same(
$this->responseChecker->getValue($this->client->show(Resources::SHIPPING_METHODS, $shippingMethod->getCode()), 'code'),
$code,
);
}
/**
* @Then I should be notified that amount for :channel channel should not be blank
*/
public function iShouldBeNotifiedThatAmountForChannelShouldNotBeBlank(ChannelInterface $channel): void
{
Assert::contains(
$this->responseChecker->getError($this->client->getLastResponse()),
sprintf('configuration[%s][amount]: This value should not be blank.', $channel->getCode()),
);
}
/**
* @Then I should be notified that code needs to contain only specific symbols
*/
public function iShouldBeNotifiedThatCodeNeedsToContainOnlySpecificSymbols(): void
{
Assert::contains(
$this->responseChecker->getError($this->client->getLastResponse()),
'code: Shipping method code can only be comprised of letters, numbers, dashes and underscores.',
);
}
/**
* @Then I should be notified that shipping charge for :channel channel cannot be lower than 0
*/
public function iShouldBeNotifiedThatShippingChargeForChannelCannotBeLowerThan0(ChannelInterface $channel): void
{
Assert::contains(
$this->responseChecker->getError($this->client->getLastResponse()),
sprintf('configuration[%s][amount]: Shipping charge cannot be lower than 0.', $channel->getCode()),
);
}
/**
* @Then I should be notified that the weight rule has an invalid configuration
*/
public function iShouldBeNotifiedThatTheWeightRuleHasAnInvalidConfiguration(): void
{
Assert::contains(
$this->responseChecker->getError($this->client->getLastResponse()),
'configuration[weight]: This value should be of type numeric.',
);
}
/**
* @Then I should be notified that the amount rule has an invalid configuration in :channel channel
*/
public function iShouldBeNotifiedThatTheAmountRuleHasAnInvalidConfigurationInChannel(ChannelInterface $channel): void
{
Assert::contains(
$this->responseChecker->getError($this->client->getLastResponse()),
sprintf('configuration[%s][amount]: This value should be of type numeric.', $channel->getCode()),
);
}
private function getAdminLocaleCode(): string
{
/** @var AdminUserInterface $adminUser */

View file

@ -124,11 +124,10 @@ final class ManagingTaxonImagesContext implements Context
*/
public function thisTaxonShouldHaveAnImageWithType(TaxonInterface $taxon, string $type): void
{
Assert::true($this->responseChecker->hasSubResourceWithValue(
Assert::true($this->responseChecker->hasValuesInAnySubresourceObjectCollection(
$this->client->show(Resources::TAXONS, $taxon->getCode()),
'images',
'type',
$type,
['type' => $type],
));
}
@ -138,11 +137,10 @@ final class ManagingTaxonImagesContext implements Context
*/
public function thisTaxonShouldNotHaveAnyImagesWithType(TaxonInterface $taxon, string $type): void
{
Assert::false($this->responseChecker->hasSubResourceWithValue(
Assert::false($this->responseChecker->hasValuesInAnySubresourceObjectCollection(
$this->client->show(Resources::TAXONS, $taxon->getCode()),
'images',
'type',
$type,
['type' => $type],
));
}

View file

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

View file

@ -37,8 +37,15 @@ final class ProductAttributeContext implements Context
public function iShouldSeeTheProductAttributeWithValue(string $attributeName, string $expectedAttribute): void
{
$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']);
}
/**
* @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
*/
@ -113,6 +128,17 @@ final class ProductAttributeContext implements Context
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
{
foreach ($this->getAttributes() as $attribute) {

View file

@ -17,6 +17,8 @@ final class Subresources
{
public const PROMOTION_COUPONS = 'coupons';
public const ADDRESSES_LOG_ENTRIES = 'log-entries';
private function __construct()
{
}

View file

@ -66,6 +66,7 @@ final class CartContext implements Context
* @Given /^I (?:have|had) (product "[^"]+") in the (cart)$/
* @Given /^I have (product "[^"]+") added to the (cart)$/
* @Given /^the (?:customer|visitor) has (product "[^"]+") in the (cart)$/
* @Given /^the (?:customer|visitor) added ("[^"]+" product) to the (cart)$/
* @When /^the (?:customer|visitor) try to add (product "[^"]+") in the customer (cart)$/
*/
public function iAddedProductToTheCart(ProductInterface $product, ?string $tokenValue): void

View file

@ -14,6 +14,7 @@ declare(strict_types=1);
namespace Sylius\Behat\Context\Setup;
use Behat\Behat\Context\Context;
use Behat\Step\Given;
use Doctrine\Persistence\ObjectManager;
use Sylius\Behat\Service\Setter\ChannelContextSetterInterface;
use Sylius\Behat\Service\SharedStorageInterface;
@ -26,10 +27,12 @@ use Sylius\Component\Core\Model\ShopBillingData;
use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Core\Test\Services\DefaultChannelFactoryInterface;
use Sylius\Component\Locale\Model\LocaleInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
final class ChannelContext implements Context
{
/**
* @param ChannelRepositoryInterface<ChannelInterface> $channelRepository
*/
public function __construct(
private SharedStorageInterface $sharedStorage,
private ChannelContextSetterInterface $channelContextSetter,
@ -37,7 +40,6 @@ final class ChannelContext implements Context
private DefaultChannelFactoryInterface $defaultChannelFactory,
private ChannelRepositoryInterface $channelRepository,
private ObjectManager $channelManager,
private RepositoryInterface $localeRepository,
) {
}
@ -69,7 +71,7 @@ final class ChannelContext implements Context
/**
* @Given the store operates on a single channel in "United States"
*/
public function storeOperatesOnASingleChannelInUnitedStates()
public function storeOperatesOnASingleChannelInUnitedStates(): void
{
$defaultData = $this->unitedStatesChannelFactory->create();
@ -78,9 +80,9 @@ final class ChannelContext implements Context
}
/**
* @Given the store operates on a single channel in the "United States" named :channelIdentifier
* @Given the store operates on a single channel in the "United States" named :channelName
*/
public function storeOperatesOnASingleChannelInTheUnitedStatesNamed(string $channelName)
public function storeOperatesOnASingleChannelInTheUnitedStatesNamed(string $channelName): void
{
$channelCode = StringInflector::nameToLowercaseCode($channelName);
$defaultData = $this->unitedStatesChannelFactory->create($channelCode, $channelName);
@ -93,7 +95,7 @@ final class ChannelContext implements Context
* @Given the store operates on a single channel
* @Given the store operates on a single channel in :currencyCode currency
*/
public function storeOperatesOnASingleChannel($currencyCode = null)
public function storeOperatesOnASingleChannel(?string $currencyCode = null): void
{
$defaultData = $this->defaultChannelFactory->create(null, null, $currencyCode);
@ -137,7 +139,7 @@ final class ChannelContext implements Context
/**
* @Given the channel :channel is enabled
*/
public function theChannelIsEnabled(ChannelInterface $channel)
public function theChannelIsEnabled(ChannelInterface $channel): void
{
$this->changeChannelState($channel, true);
}
@ -146,7 +148,7 @@ final class ChannelContext implements Context
* @Given the channel :channel is disabled
* @Given the channel :channel has been disabled
*/
public function theChannelIsDisabled(ChannelInterface $channel)
public function theChannelIsDisabled(ChannelInterface $channel): void
{
$this->changeChannelState($channel, false);
}
@ -154,7 +156,7 @@ final class ChannelContext implements Context
/**
* @Given /^the (channel "[^"]+") has showing the lowest price of discounted products (enabled|disabled)$/
*/
public function theChannelHasShowingTheLowestPriceOfDiscountedProducts(ChannelInterface $channel, string $visible)
public function theChannelHasShowingTheLowestPriceOfDiscountedProducts(ChannelInterface $channel, string $visible): void
{
$channel->getChannelPriceHistoryConfig()->setLowestPriceForDiscountedProductsVisible($visible === 'enabled');
@ -164,7 +166,7 @@ final class ChannelContext implements Context
/**
* @Given channel :channel has been deleted
*/
public function iChannelHasBeenDeleted(ChannelInterface $channel)
public function iChannelHasBeenDeleted(ChannelInterface $channel): void
{
$this->channelRepository->remove($channel);
}
@ -172,7 +174,7 @@ final class ChannelContext implements Context
/**
* @Given /^(its) default tax zone is (zone "([^"]+)")$/
*/
public function itsDefaultTaxRateIs(ChannelInterface $channel, ZoneInterface $defaultTaxZone)
public function itsDefaultTaxRateIs(ChannelInterface $channel, ZoneInterface $defaultTaxZone): void
{
$channel->setDefaultTaxZone($defaultTaxZone);
$this->channelManager->flush();
@ -182,7 +184,7 @@ final class ChannelContext implements Context
* @Given /^(this channel) has contact email set as "([^"]+)"$/
* @Given /^(this channel) has no contact email set$/
*/
public function thisChannelHasContactEmailSetAs(ChannelInterface $channel, $contactEmail = null)
public function thisChannelHasContactEmailSetAs(ChannelInterface $channel, ?string $contactEmail = null): void
{
$channel->setContactEmail($contactEmail);
$this->channelManager->flush();
@ -191,7 +193,7 @@ final class ChannelContext implements Context
/**
* @Given /^on (this channel) shipping step is skipped if only a single shipping method is available$/
*/
public function onThisChannelShippingStepIsSkippedIfOnlyASingleShippingMethodIsAvailable(ChannelInterface $channel)
public function onThisChannelShippingStepIsSkippedIfOnlyASingleShippingMethodIsAvailable(ChannelInterface $channel): void
{
$channel->setSkippingShippingStepAllowed(true);
@ -203,7 +205,7 @@ final class ChannelContext implements Context
*/
public function onThisChannelPaymentStepIsSkippedIfOnlyASinglePaymentMethodIsAvailable(
ChannelInterface $channel,
) {
): void {
$channel->setSkippingPaymentStepAllowed(true);
$this->channelManager->flush();
@ -368,10 +370,7 @@ final class ChannelContext implements Context
$this->channelManager->flush();
}
/**
* @param bool $state
*/
private function changeChannelState(ChannelInterface $channel, $state)
private function changeChannelState(ChannelInterface $channel, bool $state): void
{
$channel->setEnabled($state);
$this->channelManager->flush();

View file

@ -44,6 +44,7 @@ use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Sylius\Component\Shipping\Repository\ShippingMethodRepositoryInterface;
use Sylius\Component\Shipping\ShipmentTransitions;
use Symfony\Component\Clock\ClockInterface;
use Webmozart\Assert\Assert;
final class OrderContext implements Context
@ -64,6 +65,7 @@ final class OrderContext implements Context
private ProductVariantResolverInterface $variantResolver,
private OrderItemQuantityModifierInterface $itemQuantityModifier,
private ObjectManager $objectManager,
private ClockInterface $clock,
) {
}
@ -558,7 +560,7 @@ final class OrderContext implements Context
}
/**
* @Given /^(\d+) customers have added products to the cart for total of ("[^"]+")$/
* @Given /^(\d+) new customers have added products to the cart for total of ("[^"]+")$/
*/
public function customersHaveAddedProductsToTheCartForTotalOf(int $numberOfCustomers, int $total): void
{
@ -589,7 +591,7 @@ final class OrderContext implements Context
}
/**
* @Given /^(\d+) (?:|more )customers have placed (\d+) orders for total of ("[^"]+")$/
* @Given /^(\d+) (?:|more )new customers have placed (\d+) orders for total of ("[^"]+")$/
*/
public function customersHavePlacedOrdersForTotalOf(int $numberOfCustomers, int $numberOfOrders, int $total): void
{
@ -597,7 +599,7 @@ final class OrderContext implements Context
}
/**
* @Given /^(\d+) customers have fulfilled (\d+) orders placed for total of ("[^"]+")$/
* @Given /^(\d+) new customers have fulfilled (\d+) orders placed for total of ("[^"]+")$/
*/
public function customersHaveFulfilledOrdersPlacedForTotalOf(
int $numberOfCustomers,
@ -608,7 +610,7 @@ final class OrderContext implements Context
}
/**
* @Given /^(\d+) (?:|more )customers have placed (\d+) orders for total of ("[^"]+") mostly ("[^"]+" product)$/
* @Given /^(\d+) (?:|more )new customers have placed (\d+) orders for total of ("[^"]+") mostly ("[^"]+" product)$/
*/
public function customersHavePlacedOrdersForTotalOfMostlyProduct(
int $numberOfCustomers,
@ -620,7 +622,7 @@ final class OrderContext implements Context
}
/**
* @Given /^(\d+) (?:|more )customers have fulfilled (\d+) orders placed for total of ("[^"]+") mostly ("[^"]+" product)$/
* @Given /^(\d+) (?:|more )new customers have fulfilled (\d+) orders placed for total of ("[^"]+") mostly ("[^"]+" product)$/
*/
public function customersHaveFulfilledOrdersPlacedForTotalOfMostlyProduct(
int $numberOfCustomers,
@ -632,7 +634,7 @@ final class OrderContext implements Context
}
/**
* @Given /^(\d+) (?:|more )customers have paid (\d+) orders placed for total of ("[^"]+")$/
* @Given /^(\d+) (?:|more )new customers have paid (\d+) orders placed for total of ("[^"]+")$/
*/
public function moreCustomersHavePaidOrdersPlacedForTotalOf(
int $numberOfCustomers,
@ -918,6 +920,8 @@ final class OrderContext implements Context
$customer->setFirstname('John');
$customer->setLastname('Doe' . $i);
$customer->setCreatedAt($this->clock->now());
$customers[] = $customer;
$this->customerRepository->add($customer);
@ -1020,6 +1024,8 @@ final class OrderContext implements Context
$this->shipOrder($order);
}
$order->setCheckoutCompletedAt($this->clock->now());
$this->objectManager->persist($order);
$this->sharedStorage->set('order', $order);
}

View file

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

View file

@ -423,6 +423,59 @@ final class ProductAttributeContext implements Context
$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(
string $type,
string $name,

View file

@ -15,10 +15,14 @@ namespace Sylius\Behat\Context\Transform;
use Behat\Behat\Context\Context;
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
use Sylius\Component\Core\Model\ChannelInterface;
use Webmozart\Assert\Assert;
final class ChannelContext implements Context
{
/**
* @param ChannelRepositoryInterface<ChannelInterface> $channelRepository
*/
public function __construct(private ChannelRepositoryInterface $channelRepository)
{
}
@ -29,7 +33,7 @@ final class ChannelContext implements Context
* @Transform /^channel to "([^"]+)"$/
* @Transform :channel
*/
public function getChannelByName($channelName)
public function getChannelByName(string $channelName)
{
$channels = $this->channelRepository->findByName($channelName);
@ -44,8 +48,10 @@ final class ChannelContext implements Context
/**
* @Transform all channels
*
* @return array<ChannelInterface>
*/
public function getAllChannels()
public function getAllChannels(): array
{
return $this->channelRepository->findAll();
}

View file

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

View file

@ -16,7 +16,7 @@ namespace Sylius\Behat\Context\Ui\Admin;
use Behat\Behat\Context\Context;
use FriendsOfBehat\PageObjectExtension\Page\UnexpectedPageException;
use Sylius\Behat\Page\Admin\DashboardPageInterface;
use Sylius\Component\Core\Formatter\StringInflector;
use Sylius\Component\Core\Model\ChannelInterface;
use Webmozart\Assert\Assert;
final class DashboardContext implements Context
@ -28,8 +28,9 @@ final class DashboardContext implements Context
/**
* @Given I am on the administration dashboard
* @When I (try to )open administration dashboard
* @When I (try to )view statistics
*/
public function iOpenAdministrationDashboard(): void
public function iViewStatistics(): void
{
try {
$this->dashboardPage->open();
@ -38,17 +39,44 @@ final class DashboardContext implements Context
}
/**
* @When I open administration dashboard for :name channel
* @When I view statistics for :channel channel
*
* @throws UnexpectedPageException
*/
public function iOpenAdministrationDashboardForChannel($name)
public function iViewStatisticsForChannel(ChannelInterface $channel): void
{
$this->dashboardPage->open(['channel' => StringInflector::nameToLowercaseCode($name)]);
$this->dashboardPage->open(['channel' => $channel->getCode()]);
}
/**
* @When /^I view statistics for ("[^"]+" channel) and (current|previous|next) year split by (month|day)$/
*
* @throws UnexpectedPageException
*/
public function iViewStatisticsForChannelAndYear(
ChannelInterface $channel,
string $period,
string $interval,
): void {
$this->dashboardPage->open(['channel' => $channel->getCode()]);
match ($interval) {
'month' => $this->dashboardPage->chooseYearSplitByMonthsInterval(),
'day' => $this->dashboardPage->chooseMonthSplitByDaysInterval(),
default => throw new \InvalidArgumentException(sprintf('Interval "%s" is not supported.', $interval)),
};
match ($period) {
'previous' => $this->dashboardPage->choosePreviousPeriod(),
'next' => $this->dashboardPage->chooseNextPeriod(),
default => null,
};
}
/**
* @When I choose :channelName channel
*/
public function iChooseChannel($channelName)
public function iChooseChannel(string $channelName): void
{
$this->dashboardPage->chooseChannel($channelName);
}
@ -64,23 +92,23 @@ final class DashboardContext implements Context
/**
* @Then I should see :number new orders
*/
public function iShouldSeeNewOrders($number)
public function iShouldSeeNewOrders(int $number): void
{
Assert::same($this->dashboardPage->getNumberOfNewOrders(), (int) $number);
Assert::same($this->dashboardPage->getNumberOfNewOrders(), $number);
}
/**
* @Then I should see :number new customers
*/
public function iShouldSeeNewCustomers($number)
public function iShouldSeeNewCustomers(int $number): void
{
Assert::same($this->dashboardPage->getNumberOfNewCustomers(), (int) $number);
Assert::same($this->dashboardPage->getNumberOfNewCustomers(), $number);
}
/**
* @Then there should be total sales of :total
*/
public function thereShouldBeTotalSalesOf($total)
public function thereShouldBeTotalSalesOf(string $total): void
{
Assert::same($this->dashboardPage->getTotalSales(), $total);
}
@ -88,25 +116,29 @@ final class DashboardContext implements Context
/**
* @Then the average order value should be :value
*/
public function myAverageOrderValueShouldBe($value)
public function myAverageOrderValueShouldBe(string $value): void
{
Assert::same($this->dashboardPage->getAverageOrderValue(), $value);
Assert::same(
$this->dashboardPage->getAverageOrderValue(),
$value,
'Expected average order value to be equal to %2$s, but it is %s.',
);
}
/**
* @Then I should see :number new customers in the list
*/
public function iShouldSeeNewCustomersInTheList($number)
public function iShouldSeeNewCustomersInTheList(int $number): void
{
Assert::same($this->dashboardPage->getNumberOfNewCustomersInTheList(), (int) $number);
Assert::same($this->dashboardPage->getNumberOfNewCustomersInTheList(), $number);
}
/**
* @Then I should see :number new orders in the list
*/
public function iShouldSeeNewOrdersInTheList($number)
public function iShouldSeeNewOrdersInTheList(int $number): void
{
Assert::same($this->dashboardPage->getNumberOfNewOrdersInTheList(), (int) $number);
Assert::same($this->dashboardPage->getNumberOfNewOrdersInTheList(), $number);
}
/**

View file

@ -888,11 +888,19 @@ final class ManagingOrdersContext implements Context
}
/**
* @Then there should be :count changes in the registry
* @Then there should be :count shipping address changes in the registry
*/
public function thereShouldBeCountChangesInTheRegistry($count)
public function thereShouldBeCountShippingAddressChangesInTheRegistry(int $count): void
{
Assert::same($this->historyPage->countShippingAddressChanges(), (int) $count);
Assert::same($this->historyPage->countShippingAddressChanges(), $count);
}
/**
* @Then there should be :count billing address changes in the registry
*/
public function thereShouldBeCountBillingAddressChangesInTheRegistry(int $count): void
{
Assert::same($this->historyPage->countBillingAddressChanges(), $count);
}
/**

View file

@ -108,6 +108,7 @@ final class ManagingPaymentsContext implements Context
/**
* @Then I should see order page with details of order :order
* @Then I should see the details of order :order
*/
public function iShouldSeeOrderPageWithDetailsOfOrder(OrderInterface $order): void
{

View file

@ -18,8 +18,6 @@ use Sylius\Behat\Page\Admin\Crud\IndexPageInterface;
use Sylius\Behat\Page\Admin\ProductAttribute\CreatePageInterface;
use Sylius\Behat\Page\Admin\ProductAttribute\UpdatePageInterface;
use Sylius\Behat\Service\Resolver\CurrentPageResolverInterface;
use Sylius\Behat\Service\SharedSecurityServiceInterface;
use Sylius\Component\Core\Model\AdminUserInterface;
use Sylius\Component\Product\Model\ProductAttributeInterface;
use Webmozart\Assert\Assert;
@ -30,7 +28,6 @@ final class ManagingProductAttributesContext implements Context
private IndexPageInterface $indexPage,
private UpdatePageInterface $updatePage,
private CurrentPageResolverInterface $currentPageResolver,
private SharedSecurityServiceInterface $sharedSecurityService,
) {
}
@ -233,25 +230,6 @@ final class ManagingProductAttributesContext implements Context
$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 entries value as :min
@ -286,24 +264,6 @@ final class ManagingProductAttributesContext implements Context
// 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
*/

View file

@ -310,6 +310,7 @@ final class ManagingProductsContext implements Context
/**
* @Then the first product on the list should have :field :value
* @Then the first product on the list within this taxon should have :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 within this taxon should have :field :value
*/
public function theLastProductOnTheListShouldHave($field, $value)
{
@ -400,6 +402,17 @@ final class ManagingProductsContext implements Context
$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 :numberOfProducts products in the list

View file

@ -172,6 +172,7 @@ final class ManagingShipmentsContext implements Context
/**
* @Then I should see order page with details of order :order
* @Then I should see the details of order :order
*/
public function iShouldSeeOrderPageWithDetailsOfOrder(OrderInterface $order): void
{

View file

@ -146,6 +146,16 @@ final class ManagingShippingMethodsContext implements Context
Assert::true($this->indexPage->isSingleResourceOnPage(['name' => $shipmentMethodName]));
}
/**
* @Then the shipping method :shipmentMethodName should not appear in the registry
*/
public function theShipmentMethodShouldNotAppearInTheRegistry(string $shipmentMethodName): void
{
$this->iWantToBrowseShippingMethods();
Assert::false($this->indexPage->isSingleResourceOnPage(['name' => $shipmentMethodName]));
}
/**
* @Given /^(this shipping method) should still be in the registry$/
*/
@ -235,7 +245,7 @@ final class ManagingShippingMethodsContext implements Context
/**
* @Then I should be notified that code needs to contain only specific symbols
*/
public function iShouldBeNotifiedThatCodeShouldContain()
public function iShouldBeNotifiedThatCodeNeedsToContainOnlySpecificSymbols(): void
{
$this->assertFieldValidationMessage(
'code',
@ -324,8 +334,9 @@ final class ManagingShippingMethodsContext implements Context
/**
* @Then I should be notified that :element has to be selected
* @Then I should be notified that the :element is required
*/
public function iShouldBeNotifiedThatElementHasToBeSelected($element)
public function iShouldBeNotifiedThatElementHasToBeSelected(string $element): void
{
$this->assertFieldValidationMessage($element, sprintf('Please select shipping method %s.', $element));
}
@ -484,6 +495,15 @@ final class ManagingShippingMethodsContext implements Context
$this->createPage->fillRuleOption('Weight', (string) $weight);
}
/**
* @When I add the "Total weight greater than or equal" rule configured with invalid data
*/
public function iAddTheTotalWeightGreaterThanOrEqualRuleConfiguredWithInvalidData(): void
{
$this->createPage->addRule('Total weight greater than or equal');
$this->createPage->fillRuleOption('Weight', 'invalid data');
}
/**
* @When I add the "Total weight less than or equal" rule configured with :weight
*/
@ -494,21 +514,21 @@ final class ManagingShippingMethodsContext implements Context
}
/**
* @When /^I add the "Items total greater than or equal" rule configured with (?:|£|\$)([^"]+) for ("[^"]+" channel)$/
* @When /^I add the "([^"]+)" rule configured with (?:€|£|\$)([^"]+) for ("[^"]+" channel)$/
*/
public function iAddTheItemsTotalGreaterThanOrEqualRuleConfiguredWith($value, ChannelInterface $channel): void
public function iAddTheItemsTotalLessThanOrEqualRuleConfiguredWith(string $rule, mixed $value, ChannelInterface $channel): void
{
$this->createPage->addRule('Items total greater than or equal');
$this->createPage->addRule($rule);
$this->createPage->fillRuleOptionForChannel($channel->getCode(), 'Amount', (string) $value);
}
/**
* @When /^I add the "Items total less than or equal" rule configured with (?:|£|\$)([^"]+) for ("[^"]+" channel)$/
* @When /^I add the "Items total less than or equal" rule configured with invalid data for ("[^"]+" channel)$/
*/
public function iAddTheItemsTotalLessThanOrEqualRuleConfiguredWith($value, ChannelInterface $channel): void
public function iAddTheItemsTotalLessThanOrEqualRuleConfiguredWithInvalidData(ChannelInterface $channel): void
{
$this->createPage->addRule('Items total less than or equal');
$this->createPage->fillRuleOptionForChannel($channel->getCode(), 'Amount', (string) $value);
$this->createPage->fillRuleOptionForChannel($channel->getCode(), 'Amount', 'Invalid data');
}
/**
@ -532,6 +552,28 @@ final class ManagingShippingMethodsContext implements Context
);
}
/**
* @Then I should be notified that the weight rule has an invalid configuration
*/
public function iShouldBeNotifiedThatTheWeightRuleHasAnInvalidConfiguration(): void
{
$this->assertFieldValidationMessage('weight', 'Please enter a number.');
}
/**
* @Then I should be notified that the amount rule has an invalid configuration in :channel channel
*/
public function iShouldBeNotifiedThatTheAmountRuleHasAnInvalidConfigurationInChannel(ChannelInterface $channel): void
{
/** @var CreatePageInterface|UpdatePageInterface $currentPage */
$currentPage = $this->currentPageResolver->getCurrentPageWithForm([$this->createPage, $this->updatePage]);
Assert::same(
$currentPage->getValidationMessageForRuleAmount($channel->getCode()),
'Please enter a valid money amount.',
);
}
/**
* @param string $element
* @param string $expectedMessage

View file

@ -60,9 +60,9 @@ final class ProductShowPageContext implements Context
}
/**
* @When I access :product product page
* @When I access the :product product
*/
public function iAccessProductPage(ProductInterface $product): void
public function iAccessTheProduct(ProductInterface $product): void
{
$this->indexPage->showProductPage($product->getName());
}
@ -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));
}
@ -385,7 +385,15 @@ final class ProductShowPageContext implements Context
*/
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
*/
@ -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
{

View file

@ -18,9 +18,13 @@ use FriendsOfBehat\PageObjectExtension\Element\Element;
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');
/** @var NodeElement $product */

View file

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

View file

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

View file

@ -26,6 +26,20 @@ final class VariantsElement extends Element implements VariantsElementInterface
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(
string $name,
string $code,

View file

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

View file

@ -13,6 +13,7 @@ declare(strict_types=1);
namespace Sylius\Behat\Page\Admin;
use Behat\Mink\Exception\ElementNotFoundException;
use Behat\Mink\Session;
use FriendsOfBehat\PageObjectExtension\Page\SymfonyPage;
use Sylius\Behat\Service\Accessor\TableAccessorInterface;
@ -20,84 +21,133 @@ use Symfony\Component\Routing\RouterInterface;
class DashboardPage extends SymfonyPage implements DashboardPageInterface
{
/**
* @template TKey of array-key
* @template TValue
*
* @param array<TKey, TValue>|\ArrayAccess<TKey, TValue> $minkParameters
*/
public function __construct(
Session $session,
$minkParameters,
array|\ArrayAccess $minkParameters,
RouterInterface $router,
protected TableAccessorInterface $tableAccessor,
) {
parent::__construct($session, $minkParameters, $router);
}
/** @throws ElementNotFoundException */
public function getTotalSales(): string
{
return $this->getElement('total_sales')->getText();
}
/** @throws ElementNotFoundException */
public function getNumberOfNewOrders(): int
{
return (int) $this->getElement('new_orders')->getText();
}
/** @throws ElementNotFoundException */
public function getNumberOfNewOrdersInTheList(): int
{
return $this->tableAccessor->countTableBodyRows($this->getElement('order_list'));
}
/** @throws ElementNotFoundException */
public function getNumberOfNewCustomers(): int
{
return (int) $this->getElement('new_customers')->getText();
}
/** @throws ElementNotFoundException */
public function getNumberOfNewCustomersInTheList(): int
{
return $this->tableAccessor->countTableBodyRows($this->getElement('customer_list'));
}
/** @throws ElementNotFoundException */
public function getAverageOrderValue(): string
{
return $this->getElement('average_order_value')->getText();
}
/** @throws ElementNotFoundException */
public function getSubHeader(): string
{
return trim($this->getElement('sub_header')->getText());
}
/** @throws ElementNotFoundException */
public function isSectionWithLabelVisible(string $name): bool
{
return $this->getElement('admin_menu')->find('css', sprintf('div:contains(%s)', $name)) !== null;
}
/** @throws ElementNotFoundException */
public function logOut(): void
{
$this->getElement('logout')->click();
}
/** @throws ElementNotFoundException */
public function chooseChannel(string $channelName): void
{
$this->getElement('channel_choosing_link', ['%channelName%' => $channelName])->click();
}
/** @throws ElementNotFoundException */
public function chooseYearSplitByMonthsInterval(): void
{
$this->getElement('year_split_by_months_statistics_button')->click();
}
/** @throws ElementNotFoundException */
public function chooseMonthSplitByDaysInterval(): void
{
$this->getElement('month_split_by_days_statistics_button')->click();
}
/** @throws ElementNotFoundException */
public function choosePreviousPeriod(): void
{
$this->getElement('navigation_previous')->click();
usleep(500000);
}
/** @throws ElementNotFoundException */
public function chooseNextPeriod(): void
{
$this->getElement('navigation_next')->click();
usleep(500000);
}
public function getRouteName(): string
{
return 'sylius_admin_dashboard';
}
/** @return array<string, string> */
protected function getDefinedElements(): array
{
return array_merge(parent::getDefinedElements(), [
'admin_menu' => '.sylius-admin-menu',
'average_order_value' => '#average-order-value',
'channel_choosing_link' => 'a:contains("%channelName%")',
'customer_list' => '#customers',
'dropdown' => 'i.dropdown',
'logout' => '#sylius-logout-button',
'month_split_by_days_statistics_button' => 'button[data-stats-button="month"]',
'navigation_next' => '#navigation-next',
'navigation_previous' => '#navigation-prev',
'new_customers' => '#new-customers',
'new_orders' => '#new-orders',
'order_list' => '#orders',
'total_sales' => '#total-sales',
'sub_header' => '.ui.header .content .sub.header',
'channel_choosing_link' => 'a:contains("%channelName%")',
'admin_menu' => '.sylius-admin-menu',
'total_sales' => '#total-sales',
'year_split_by_months_statistics_button' => 'button[data-stats-button="year"]',
]);
}
}

View file

@ -36,4 +36,12 @@ interface DashboardPageInterface extends SymfonyPageInterface
public function logOut(): void;
public function chooseChannel(string $channelName): void;
public function chooseYearSplitByMonthsInterval(): void;
public function chooseMonthSplitByDaysInterval(): void;
public function choosePreviousPeriod(): void;
public function chooseNextPeriod(): void;
}

View file

@ -26,4 +26,9 @@ class HistoryPage extends SymfonyPage implements HistoryPageInterface
{
return count($this->getDocument()->findAll('css', '#shipping-address-changes tbody tr'));
}
public function countBillingAddressChanges(): int
{
return count($this->getDocument()->findAll('css', '#billing-address-changes tbody tr'));
}
}

View file

@ -18,4 +18,6 @@ use FriendsOfBehat\PageObjectExtension\Page\SymfonyPageInterface;
interface HistoryPageInterface extends SymfonyPageInterface
{
public function countShippingAddressChanges(): int;
public function countBillingAddressChanges(): int;
}

View file

@ -75,21 +75,15 @@ class CreatePage extends BaseCreatePage implements CreatePageInterface
public function getValidationMessageForAmount(string $channelCode): string
{
$foundElement = $this->getFieldElement('amount', ['%channelCode%' => $channelCode]);
if (null === $foundElement) {
throw new ElementNotFoundException($this->getSession(), 'Field element');
}
$validationMessage = $foundElement->find('css', '.sylius-validation-error');
if (null === $validationMessage) {
throw new ElementNotFoundException(
$this->getSession(),
'Validation message',
'css',
'.sylius-validation-error',
);
}
return $this->getValidationMessageForElement($foundElement);
}
return $validationMessage->getText();
public function getValidationMessageForRuleAmount(string $channelCode): string
{
$foundElement = $this->getChannelConfigurationOfLastRule($channelCode);
return $this->getValidationMessageForElement($foundElement);
}
public function addRule(string $ruleName): void
@ -126,6 +120,7 @@ class CreatePage extends BaseCreatePage implements CreatePageInterface
'channel' => '#sylius_shipping_method_channels .ui.checkbox:contains("%channel%")',
'calculator' => '#sylius_shipping_method_calculator',
'calculator_configuration' => '.ui.segment.configuration',
'weight' => '[id*="sylius_shipping_method_rules_"][id*="_configuration_weight"]',
'code' => '#sylius_shipping_method_code',
'name' => '#sylius_shipping_method_translations_en_US_name',
'zone' => '#sylius_shipping_method_zone',
@ -133,6 +128,25 @@ class CreatePage extends BaseCreatePage implements CreatePageInterface
]);
}
private function getValidationMessageForElement(NodeElement $element): string
{
if (null === $element) {
throw new ElementNotFoundException($this->getSession(), 'Field element');
}
$validationMessage = $element->find('css', '.sylius-validation-error');
if (null === $validationMessage) {
throw new ElementNotFoundException(
$this->getSession(),
'Validation message',
'css',
'.sylius-validation-error',
);
}
return $validationMessage->getText();
}
/**
* @throws ElementNotFoundException
*/
@ -167,7 +181,7 @@ class CreatePage extends BaseCreatePage implements CreatePageInterface
return $items;
}
private function getChannelConfigurationOfLastRule(string $channelCode): NodeElement
private function getChannelConfigurationOfLastRule(string $channelCode): ?NodeElement
{
return $this
->getLastCollectionItem('rules')

View file

@ -32,13 +32,14 @@ interface CreatePageInterface extends BaseCreatePageInterface
public function chooseCalculator(string $name): void;
public function checkChannel($channelName): void;
public function checkChannel(string $channelName): void;
/**
* @throws ElementNotFoundException
*/
/** @throws ElementNotFoundException */
public function getValidationMessageForAmount(string $channelCode): string;
/** @throws ElementNotFoundException */
public function getValidationMessageForRuleAmount(string $channelCode): string;
public function addRule(string $ruleName): void;
public function selectRuleOption(string $option, string $value, bool $multiple = false): void;

View file

@ -62,6 +62,8 @@
<service id="Sylius\Behat\Context\Api\Admin\ManagingProductTaxonsContext">
<argument type="service" id="sylius.behat.api_platform_client.admin" />
<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 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" />
</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">
<argument type="service" id="sylius.behat.api_platform_client.admin" />
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
@ -195,6 +206,7 @@
<argument type="service" id="sylius.behat.api_platform_client.admin" />
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
<argument type="service" id="sylius.behat.section_iri_converter" />
<argument type="service" id="api_platform.iri_converter" />
<argument>%sylius.security.new_api_route%</argument>
</service>
@ -210,10 +222,12 @@
<service id="sylius.behat.context.api.admin.managing_orders" class="Sylius\Behat\Context\Api\Admin\ManagingOrdersContext">
<argument type="service" id="sylius.behat.api_platform_client.admin" />
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
<argument type="service" id="sylius.behat.request_factory" />
<argument type="service" id="api_platform.iri_converter" />
<argument type="service" id="sylius.behat.api_security" />
<argument type="service" id="sylius.behat.shared_storage" />
<argument type="service" id="sylius.behat.api.shared_security" />
<argument type="service" id="sylius.behat.section_iri_converter" />
</service>
<service id="Sylius\Behat\Context\Api\Admin\ManagingPaymentMethodsContext">
@ -291,7 +305,7 @@
<argument type="service" id="api_platform.iri_converter" />
</service>
<service id="Sylius\Behat\Context\Api\Admin\ManagingPlacedOrderAddressesContext">
<service id="sylius.behat.context.api.admin.managing_placed_order_addresses" class="Sylius\Behat\Context\Api\Admin\ManagingPlacedOrderAddressesContext">
<argument type="service" id="sylius.behat.api_platform_client.admin" />
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
<argument type="service" id="sylius.behat.shared_storage" />

View file

@ -58,7 +58,6 @@
<argument type="service" id="sylius.behat.factory.default_channel" />
<argument type="service" id="sylius.repository.channel" />
<argument type="service" id="sylius.manager.channel" />
<argument type="service" id="sylius.repository.locale" />
</service>
<service id="sylius.behat.context.setup.currency" class="Sylius\Behat\Context\Setup\CurrencyContext">
@ -124,6 +123,7 @@
<argument type="service" id="Sylius\Component\Product\Resolver\ProductVariantResolverInterface" />
<argument type="service" id="sylius.order_item_quantity_modifier" />
<argument type="service" id="doctrine.orm.entity_manager" />
<argument type="service" id="sylius.behat.clock" />
</service>
<service id="sylius.behat.context.setup.payment" class="Sylius\Behat\Context\Setup\PaymentContext">

View file

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

View file

@ -30,6 +30,7 @@ imports:
- suites/api/locale/managing_locales.yml
- suites/api/order/managing_orders.yml
- suites/api/order/modifying_placed_order_address.yaml
- suites/api/order/order_history.yaml
- suites/api/payment/managing_payment_methods.yaml
- suites/api/payment/managing_payments.yml
- suites/api/product/adding_product_review.yml
@ -90,7 +91,7 @@ imports:
- suites/ui/account/login.yml
- suites/ui/addressing/managing_countries.yml
- suites/ui/addressing/managing_zones.yml
- suites/ui/admin/dashboard.yml
- suites/ui/admin/dashboard.yaml
- suites/ui/admin/impersonating_customers.yml
- suites/ui/admin/locale.yml
- suites/ui/admin/login.yml

View file

@ -17,6 +17,7 @@ default:
- sylius.behat.context.setup.currency
- sylius.behat.context.setup.customer
- sylius.behat.context.setup.geographical
- sylius.behat.context.setup.locale
- sylius.behat.context.setup.order
- sylius.behat.context.setup.payment
- sylius.behat.context.setup.product

View file

@ -36,9 +36,9 @@ default:
- sylius.behat.context.transform.shared_storage
- sylius.behat.context.api.admin.managing_orders
- sylius.behat.context.api.admin.managing_placed_order_addresses
- sylius.behat.context.api.admin.response
- sylius.behat.context.api.admin.save
- Sylius\Behat\Context\Api\Admin\ManagingPlacedOrderAddressesContext
filters:
tags: "@modifying_placed_order_address&&@api"

View file

@ -0,0 +1,37 @@
# This file is part of the Sylius package.
# (c) Sylius Sp. z o.o.
default:
suites:
api_order_history:
contexts:
- sylius.behat.context.hook.doctrine_orm
- sylius.behat.context.hook.session
- sylius.behat.context.setup.admin_api_security
- sylius.behat.context.setup.channel
- sylius.behat.context.setup.order
- sylius.behat.context.setup.payment
- sylius.behat.context.setup.product
- sylius.behat.context.setup.shipping
- sylius.behat.context.transform.address
- sylius.behat.context.transform.channel
- sylius.behat.context.transform.country
- sylius.behat.context.transform.customer
- sylius.behat.context.transform.lexical
- sylius.behat.context.transform.order
- sylius.behat.context.transform.payment
- sylius.behat.context.transform.product
- sylius.behat.context.transform.shipping_method
- sylius.behat.context.transform.zone
- sylius.behat.context.transform.shared_storage
- sylius.behat.context.api.admin.managing_orders
- sylius.behat.context.api.admin.managing_placed_order_addresses
- sylius.behat.context.api.admin.response
- sylius.behat.context.api.admin.save
filters:
tags: "@order_history&&@api"

View file

@ -44,6 +44,7 @@ default:
- sylius.behat.context.setup.taxonomy
- 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_products
- sylius.behat.context.api.admin.response

View file

@ -8,20 +8,38 @@ default:
- sylius.behat.context.hook.doctrine_orm
- sylius.behat.context.transform.channel
- sylius.behat.context.transform.currency
- sylius.behat.context.transform.lexical
- sylius.behat.context.transform.locale
- 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_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\CatalogPromotionContext
- sylius.behat.context.setup.admin_api_security
- sylius.behat.context.setup.calendar
- sylius.behat.context.setup.channel
- sylius.behat.context.setup.locale
- sylius.behat.context.setup.product
- sylius.behat.context.setup.product_association
- sylius.behat.context.setup.product_attribute
- sylius.behat.context.setup.product_option
- sylius.behat.context.setup.product_taxon
- sylius.behat.context.setup.shipping_category
- sylius.behat.context.setup.taxation
- sylius.behat.context.setup.taxonomy
- Sylius\Behat\Context\Setup\CatalogPromotionContext
- Sylius\Behat\Context\Setup\PriceHistoryContext
- sylius.behat.context.api.admin.managing_products
- Sylius\Behat\Context\Api\Admin\BrowsingCatalogPromotionProductVariantsContext
filters:
tags: "@viewing_product_in_admin_panel&&@api"

View file

@ -16,9 +16,9 @@ default:
- sylius.behat.context.transform.product_variant
- sylius.behat.context.transform.shared_storage
- sylius.behat.context.transform.taxon
- Sylius\Behat\Context\Transform\CatalogPromotionContext
- sylius.behat.context.setup.admin_user
- sylius.behat.context.setup.admin_security
- sylius.behat.context.setup.channel
- sylius.behat.context.setup.customer
- sylius.behat.context.setup.locale
@ -27,7 +27,7 @@ default:
- sylius.behat.context.setup.product_attribute
- sylius.behat.context.setup.product_review
- 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\CatalogPromotionContext
- Sylius\Behat\Context\Setup\PriceHistoryContext

View file

@ -8,6 +8,7 @@ default:
- sylius.behat.context.hook.doctrine_orm
- sylius.behat.context.hook.session
- sylius.behat.context.setup.calendar
- sylius.behat.context.setup.channel
- sylius.behat.context.setup.currency
- sylius.behat.context.setup.geographical
@ -19,6 +20,7 @@ default:
- sylius.behat.context.setup.shipping
- sylius.behat.context.setup.zone
- sylius.behat.context.transform.channel
- sylius.behat.context.transform.customer
- sylius.behat.context.transform.lexical
- sylius.behat.context.transform.order

View file

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

View file

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

View file

@ -325,7 +325,7 @@ final class MainMenuBuilder
{
$configuration = $menu
->addChild('official_support')
->setLabel('sylius.menu.admin.main.official_support.header' )
->setLabel('sylius.menu.admin.main.official_support.header')
;
$configuration

View file

@ -0,0 +1,41 @@
<?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 Sylius\Bundle\ApiBundle\Controller;
use Doctrine\Common\Collections\Collection;
use Sylius\Bundle\ApiBundle\Query\GetAddressLogEntryCollection;
use Sylius\Component\Addressing\Model\AddressLogEntry;
use Symfony\Component\Messenger\HandleTrait;
use Symfony\Component\Messenger\MessageBusInterface;
/** @experimental */
final class GetAddressLogEntryCollectionAction
{
use HandleTrait;
public function __construct(MessageBusInterface $messageBus)
{
$this->messageBus = $messageBus;
}
/** @return Collection<array-key, AddressLogEntry> */
public function __invoke(int $id): Collection
{
return $this->handle(
$this->messageBus->dispatch(
new GetAddressLogEntryCollection($id),
),
);
}
}

View file

@ -36,6 +36,11 @@ final class Configuration implements ConfigurationInterface
->defaultFalse()
->end()
->end()
->children()
->arrayNode('order_states_to_filter_out')
->scalarPrototype()->end()
->end()
->end()
->children()
->variableNode('product_image_prefix')
->defaultValue('media/image')

View file

@ -38,6 +38,7 @@ final class SyliusApiExtension extends Extension implements PrependExtensionInte
'sylius_api.filter_eager_loading_extension.restricted_resources',
$config['filter_eager_loading_extension']['restricted_resources'],
);
$container->setParameter('sylius_api.order_states_to_filter_out', $config['order_states_to_filter_out']);
$loader->load('services.xml');

View file

@ -0,0 +1,86 @@
<?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 Sylius\Bundle\ApiBundle\Doctrine\QueryExtension;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\ContextAwareQueryCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\ORM\QueryBuilder;
use Sylius\Bundle\ApiBundle\SectionResolver\AdminApiSection;
use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface;
use Sylius\Component\Core\Model\OrderInterface;
/** @experimental */
final class OrderExtension implements ContextAwareQueryCollectionExtensionInterface, QueryItemExtensionInterface
{
/**
* @param array<string> $orderStatesToFilterOut
*/
public function __construct(
private SectionProviderInterface $sectionProvider,
private array $orderStatesToFilterOut,
) {
}
/**
* @param array<mixed> $context
*/
public function applyToCollection(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
string $operationName = null,
array $context = [],
): void {
$this->filterOutOrders($queryBuilder, $queryNameGenerator, $resourceClass);
}
/**
* @param array<mixed> $context
* @param array<mixed> $identifiers
*/
public function applyToItem(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
array $identifiers,
string $operationName = null,
array $context = [],
): void {
$this->filterOutOrders($queryBuilder, $queryNameGenerator, $resourceClass);
}
private function filterOutOrders(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
): void {
if (!is_a($resourceClass, OrderInterface::class, true)) {
return;
}
if (!$this->sectionProvider->getSection() instanceof AdminApiSection) {
return;
}
$stateParameter = $queryNameGenerator->generateParameterName('state');
$rootAlias = $queryBuilder->getRootAliases()[0];
$queryBuilder
->andWhere($queryBuilder->expr()->notIn(sprintf('%s.state', $rootAlias), sprintf(':%s', $stateParameter)))
->setParameter($stateParameter, $this->orderStatesToFilterOut, ArrayParameterType::STRING)
;
}
}

View file

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

Some files were not shown because too many files have changed in this diff Show more