Completed
Push — checkout-optimisation ( 4a6bfb...756f29 )
by Kamil
18:24
created

ProductContext::iSelectVariant()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sylius\Behat\Context\Ui\Shop;
13
14
use Behat\Behat\Context\Context;
15
use Behat\Mink\Element\NodeElement;
16
use Sylius\Behat\Page\Shop\Product\IndexPageInterface;
17
use Sylius\Behat\Page\Shop\Product\ShowPageInterface;
18
use Sylius\Behat\Page\Shop\ProductReview\IndexPageInterface as ProductReviewIndexPageInterface;
19
use Sylius\Component\Core\Model\ProductInterface;
20
use Sylius\Component\Core\Model\TaxonInterface;
21
use Webmozart\Assert\Assert;
22
23
/**
24
 * @author Kamil Kokot <[email protected]>
25
 * @author Magdalena Banasiak <[email protected]>
26
 * @author Anna Walasek <[email protected]>
27
 */
28
final class ProductContext implements Context
29
{
30
    /**
31
     * @var ShowPageInterface
32
     */
33
    private $showPage;
34
35
    /**
36
     * @var IndexPageInterface
37
     */
38
    private $indexPage;
39
40
    /**
41
     * @var ProductReviewIndexPageInterface
42
     */
43
    private $productReviewsIndexPage;
44
45
    /**
46
     * @param ShowPageInterface $showPage
47
     * @param IndexPageInterface $indexPage
48
     * @param ProductReviewIndexPageInterface $productReviewsIndexPage
49
     */
50
    public function __construct(
51
        ShowPageInterface $showPage,
52
        IndexPageInterface $indexPage,
53
        ProductReviewIndexPageInterface $productReviewsIndexPage
54
    ) {
55
        $this->showPage = $showPage;
56
        $this->indexPage = $indexPage;
57
        $this->productReviewsIndexPage = $productReviewsIndexPage;
58
    }
59
60
    /**
61
     * @Then I should be able to access product :product
62
     */
63
    public function iShouldBeAbleToAccessProduct(ProductInterface $product)
64
    {
65
        $this->showPage->tryToOpen(['slug' => $product->getSlug()]);
66
67
        Assert::true($this->showPage->isOpen(['slug' => $product->getSlug()]));
68
    }
69
70
    /**
71
     * @Then I should not be able to access product :product
72
     */
73
    public function iShouldNotBeAbleToAccessProduct(ProductInterface $product)
74
    {
75
        $this->showPage->tryToOpen(['slug' => $product->getSlug()]);
76
77
        Assert::false($this->showPage->isOpen(['slug' => $product->getSlug()]));
78
    }
79
80
    /**
81
     * @When /^I check (this product)'s details$/
82
     * @When /^I check (this product)'s details in the ("([^"]+)" locale)$/
83
     * @When I view product :product
84
     * @When I view product :product in the :localeCode locale
85
     */
86
    public function iOpenProductPage(ProductInterface $product, $localeCode = 'en_US')
87
    {
88
        $this->showPage->open(['slug' => $product->getTranslation($localeCode)->getSlug(), '_locale' => $localeCode]);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Resourc...el\TranslationInterface as the method getSlug() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ProductTranslation, Sylius\Component\Product\Model\ProductTranslation, Sylius\Component\Taxonomy\Model\TaxonTranslation.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
89
    }
90
91
    /**
92
     * @When /^I try to check (this product)'s details in the ("([^"]+)" locale)$/
93
     */
94
    public function iTryToOpenProductPage(ProductInterface $product, $localeCode = 'en_US')
95
    {
96
        $this->showPage->tryToOpen([
97
            'slug' => $product->getTranslation($localeCode)->getSlug(),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Resourc...el\TranslationInterface as the method getSlug() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ProductTranslation, Sylius\Component\Product\Model\ProductTranslation, Sylius\Component\Taxonomy\Model\TaxonTranslation.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
98
            '_locale' => $localeCode,
99
        ]);
100
    }
101
102
    /**
103
     * @Then /^I should not be able to view (this product) in the ("([^"]+)" locale)$/
104
     */
105
    public function iShouldNotBeAbleToViewThisProductInLocale(ProductInterface $product, $localeCode = 'en_US')
106
    {
107
        Assert::false(
108
            $this->showPage->isOpen([
109
                'slug' => $product->getTranslation($localeCode)->getSlug(),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Resourc...el\TranslationInterface as the method getSlug() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ProductTranslation, Sylius\Component\Product\Model\ProductTranslation, Sylius\Component\Taxonomy\Model\TaxonTranslation.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
110
                '_locale' => $localeCode,
111
            ])
112
        );
113
    }
114
115
    /**
116
     * @Then I should see the product name :name
117
     */
118
    public function iShouldSeeProductName($name)
119
    {
120
        Assert::same($this->showPage->getName(), $name);
121
    }
122
123
    /**
124
     * @When I open page :url
125
     */
126
    public function iOpenPage($url)
127
    {
128
        $this->showPage->visit($url);
129
    }
130
131
    /**
132
     * @Then I should be on :product product detailed page
133
     * @Then I should still be on product :product page
134
     */
135
    public function iShouldBeOnProductDetailedPage(ProductInterface $product)
136
    {
137
        Assert::true($this->showPage->isOpen(['slug' => $product->getSlug()]));
138
    }
139
140
    /**
141
     * @Then I should (also) see the product attribute :attributeName with value :expectedAttribute
142
     */
143
    public function iShouldSeeTheProductAttributeWithValue($attributeName, $expectedAttribute)
144
    {
145
        Assert::same($this->showPage->getAttributeByName($attributeName), $expectedAttribute);
146
    }
147
148
    /**
149
     * @Then I should see :count attributes
150
     */
151
    public function iShouldSeeAttributes($count)
152
    {
153
        Assert::same(count($this->getProductAttributes()), (int) $count);
154
    }
155
156
    /**
157
     * @Then the first attribute should be :name
158
     */
159
    public function theFirstAttributeShouldBe($name)
160
    {
161
        $attributes = $this->getProductAttributes();
162
163
        Assert::same(reset($attributes)->getText(), $name);
164
    }
165
166
    /**
167
     * @Then the last attribute should be :name
168
     */
169
    public function theLastAttributeShouldBe($name)
170
    {
171
        $attributes = $this->getProductAttributes();
172
173
        Assert::same(end($attributes)->getText(), $name);
174
    }
175
176
    /**
177
     * @When /^I browse products from (taxon "([^"]+)")$/
178
     */
179
    public function iCheckListOfProductsForTaxon(TaxonInterface $taxon)
180
    {
181
        $this->indexPage->open(['slug' => $taxon->getSlug()]);
182
    }
183
184
    /**
185
     * @When I search for products with name :name
186
     */
187
    public function iSearchForProductsWithName($name)
188
    {
189
        $this->indexPage->search($name);
190
    }
191
192
    /**
193
     * @When I sort products by the lowest price first
194
     */
195
    public function iSortProductsByTheLowestPriceFirst()
196
    {
197
        $this->indexPage->sort('Cheapest first');
198
    }
199
200
    /**
201
     * @When I sort products by the highest price first
202
     */
203
    public function iSortProductsByTheHighestPriceFisrt()
204
    {
205
        $this->indexPage->sort('Most expensive first');
206
    }
207
208
    /**
209
     * @When I sort products alphabetically from a to z
210
     */
211
    public function iSortProductsAlphabeticallyFromAToZ()
212
    {
213
        $this->indexPage->sort('From A to Z');
214
    }
215
216
    /**
217
     * @When I sort products alphabetically from z to a
218
     */
219
    public function iSortProductsAlphabeticallyFromZToA()
220
    {
221
        $this->indexPage->sort('From Z to A');
222
    }
223
224
    /**
225
     * @When I clear filter
226
     */
227
    public function iClearFilter()
228
    {
229
        $this->indexPage->clearFilter();
230
    }
231
232
    /**
233
     * @Then I should see the product :productName
234
     */
235
    public function iShouldSeeProduct($productName)
236
    {
237
        Assert::true($this->indexPage->isProductOnList($productName));
238
    }
239
240
    /**
241
     * @Then I should not see the product :productName
242
     */
243
    public function iShouldNotSeeProduct($productName)
244
    {
245
        Assert::false($this->indexPage->isProductOnList($productName));
246
    }
247
248
    /**
249
     * @Then I should see empty list of products
250
     */
251
    public function iShouldSeeEmptyListOfProducts()
252
    {
253
        Assert::true($this->indexPage->isEmpty());
254
    }
255
256
    /**
257
     * @Then I should see that it is out of stock
258
     */
259
    public function iShouldSeeItIsOutOfStock()
260
    {
261
        Assert::true($this->showPage->isOutOfStock());
262
    }
263
264
    /**
265
     * @Then I should be unable to add it to the cart
266
     */
267
    public function iShouldBeUnableToAddItToTheCart()
268
    {
269
        Assert::false($this->showPage->hasAddToCartButton());
270
    }
271
272
    /**
273
     * @Then the product price should be :price
274
     * @Then I should see the product price :price
275
     */
276
    public function iShouldSeeTheProductPrice($price)
277
    {
278
        Assert::same($this->showPage->getPrice(), $price);
279
    }
280
281
    /**
282
     * @When I set its :optionName to :optionValue
283
     */
284
    public function iSetItsOptionTo($optionName, $optionValue)
285
    {
286
        $this->showPage->selectOption($optionName, $optionValue);
287
    }
288
289
    /**
290
     * @When I select :variantName variant
291
     */
292
    public function iSelectVariant($variantName)
293
    {
294
        $this->showPage->selectVariant($variantName);
295
    }
296
297
    /**
298
     * @Then its current variant should be named :name
299
     */
300
    public function itsCurrentVariantShouldBeNamed($name)
301
    {
302
        Assert::same($this->showPage->getCurrentVariantName(), $name);
303
    }
304
305
    /**
306
     * @Then I should see the product :productName with price :productPrice
307
     */
308
    public function iShouldSeeTheProductWithPrice($productName, $productPrice)
309
    {
310
        Assert::same($this->indexPage->getProductPrice($productName), $productPrice);
311
    }
312
313
    /**
314
     * @Then /^I should be notified that (this product) does not have sufficient stock$/
315
     */
316
    public function iShouldBeNotifiedThatThisProductDoesNotHaveSufficientStock(ProductInterface $product)
317
    {
318
        Assert::true($this->showPage->hasProductOutOfStockValidationMessage($product));
319
    }
320
321
    /**
322
     * @Then /^I should not be notified that (this product) does not have sufficient stock$/
323
     */
324
    public function iShouldNotBeNotifiedThatThisProductDoesNotHaveSufficientStock(ProductInterface $product)
325
    {
326
        Assert::false($this->showPage->hasProductOutOfStockValidationMessage($product));
327
    }
328
329
    /**
330
     * @Then I should see a main image
331
     */
332
    public function iShouldSeeAMainImage()
333
    {
334
        Assert::true($this->showPage->isMainImageDisplayed());
335
    }
336
337
    /**
338
     * @When /^I view (oldest|newest) products from (taxon "([^"]+)")$/
339
     */
340
    public function iViewSortedProductsFromTaxon($sortDirection, TaxonInterface $taxon)
341
    {
342
        $sorting = ['createdAt' => 'oldest' === $sortDirection ? 'asc' : 'desc'];
343
344
        $this->indexPage->open(['slug' => $taxon->getSlug(), 'sorting' => $sorting]);
345
    }
346
347
    /**
348
     * @Then I should see :numberOfProducts products in the list
349
     */
350
    public function iShouldSeeProductsInTheList($numberOfProducts)
351
    {
352
        Assert::same($this->indexPage->countProductsItems(), (int) $numberOfProducts);
353
    }
354
355
    /**
356
     * @Then I should see a product with name :name
357
     */
358
    public function iShouldSeeProductWithName($name)
359
    {
360
        Assert::true($this->indexPage->isProductOnPageWithName($name));
361
    }
362
363
    /**
364
     * @Then the first product on the list should have name :name
365
     */
366
    public function theFirstProductOnTheListShouldHaveName($name)
367
    {
368
        Assert::same($this->indexPage->getFirstProductNameFromList(), $name);
369
    }
370
371
    /**
372
     * @Then the first product on the list should have name :name and price :price
373
     */
374
    public function theFirstProductOnTheListShouldHaveNameAndPrice($name, $price)
375
    {
376
        Assert::same($this->indexPage->getFirstProductNameFromList(), $name);
377
        Assert::same($this->indexPage->getProductPrice($name), $price);
378
    }
379
380
    /**
381
     * @Then the last product on the list should have name :name
382
     */
383
    public function theLastProductOnTheListShouldHaveName($name)
384
    {
385
        Assert::same($this->indexPage->getLastProductNameFromList(), $name);
386
    }
387
388
    /**
389
     * @Then the last product on the list should have name :name and price :price
390
     */
391
    public function theLastProductOnTheListShouldHaveNameAndPrice($name, $price)
392
    {
393
        Assert::same($this->indexPage->getLastProductNameFromList(), $name);
394
        Assert::same($this->indexPage->getProductPrice($name), $price);
395
    }
396
397
    /**
398
     * @Then I should see :count product reviews
399
     */
400
    public function iShouldSeeProductReviews($count)
401
    {
402
        Assert::same($this->showPage->countReviews(), (int) $count);
403
    }
404
405
    /**
406
     * @Then I should see reviews titled :firstReview, :secondReview and :thirdReview
407
     */
408
    public function iShouldSeeReviewsTitled(...$reviews)
409
    {
410
        foreach ($reviews as $review) {
411
            Assert::true(
412
                $this->showPage->hasReviewTitled($review),
413
                sprintf('Product should have review titled "%s" but it does not.', $review)
414
            );
415
        }
416
    }
417
418
    /**
419
     * @Then I should not see review titled :title
420
     */
421
    public function iShouldNotSeeReviewTitled($title)
422
    {
423
        Assert::false($this->showPage->hasReviewTitled($title));
424
    }
425
426
    /**
427
     * @When /^I check (this product)'s reviews$/
428
     */
429
    public function iCheckThisProductSReviews(ProductInterface $product)
430
    {
431
        $this->productReviewsIndexPage->open(['slug' => $product->getSlug()]);
432
    }
433
434
    /**
435
     * @Then /^I should see (\d+) product reviews in the list$/
436
     */
437
    public function iShouldSeeNumberOfProductReviewsInTheList($count)
438
    {
439
        Assert::same($this->productReviewsIndexPage->countReviews(), (int) $count);
440
    }
441
442
    /**
443
     * @Then I should not see review titled :title in the list
444
     */
445
    public function iShouldNotSeeReviewTitledInTheList($title)
446
    {
447
        Assert::false($this->productReviewsIndexPage->hasReviewTitled($title));
448
    }
449
450
    /**
451
     * @Then /^I should be notified that there are no reviews$/
452
     */
453
    public function iShouldBeNotifiedThatThereAreNoReviews()
454
    {
455
        Assert::true($this->productReviewsIndexPage->hasNoReviewsMessage());
456
    }
457
458
    /**
459
     * @Then I should see :rating as its average rating
460
     */
461
    public function iShouldSeeAsItsAverageRating($rating)
462
    {
463
        Assert::same($this->showPage->getAverageRating(), (float) $rating);
464
    }
465
466
    /**
467
     * @Then /^I should(?:| also) see the product association "([^"]+)" with (products "[^"]+" and "[^"]+")$/
468
     */
469
    public function iShouldSeeTheProductAssociationWithProducts($productAssociationName, array $products)
470
    {
471
        Assert::true(
472
            $this->showPage->hasAssociation($productAssociationName),
473
            sprintf('There should be an association named "%s" but it does not.', $productAssociationName)
474
        );
475
476
        foreach ($products as $product) {
477
            $this->assertProductIsInAssociation($product->getName(), $productAssociationName);
478
        }
479
    }
480
481
    /**
482
     * @Then /^average rating of (product "[^"]+") should be (\d+)$/
483
     */
484
    public function thisProductAverageRatingShouldBe(ProductInterface $product, $averageRating)
485
    {
486
        $this->showPage->tryToOpen(['slug' => $product->getSlug()]);
487
        $this->iShouldSeeAsItsAverageRating($averageRating);
488
    }
489
490
    /**
491
     * @Then they should have order like :firstProductName, :secondProductName and :thirdProductName
492
     */
493
    public function theyShouldHaveOrderLikeAnd(...$productNames)
494
    {
495
        Assert::true($this->indexPage->hasProductsInOrder($productNames));
496
    }
497
498
    /**
499
     * @param string $productName
500
     * @param string $productAssociationName
501
     *
502
     * @throws \InvalidArgumentException
503
     */
504
    private function assertProductIsInAssociation($productName, $productAssociationName)
505
    {
506
        Assert::true(
507
            $this->showPage->hasProductInAssociation($productName, $productAssociationName),
508
            sprintf(
509
                'There should be an associated product "%s" under association "%s" but it does not.',
510
                $productName,
511
                $productAssociationName
512
            )
513
        );
514
    }
515
516
    /**
517
     * @return NodeElement[]
518
     *
519
     * @throws \InvalidArgumentException
520
     */
521
    private function getProductAttributes()
522
    {
523
        $attributes = $this->showPage->getAttributes();
524
        Assert::notNull($attributes, 'The product has no attributes.');
525
526
        return $attributes;
527
    }
528
}
529