Completed
Push — master ( b55ca8...f3fba8 )
by Kamil
20:16 queued 07:20
created

ProductContext::iShouldNotSeeTheProductAttribute()   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
declare(strict_types=1);
13
14
namespace Sylius\Behat\Context\Ui\Shop;
15
16
use Behat\Behat\Context\Context;
17
use Behat\Mink\Element\NodeElement;
18
use Sylius\Behat\Page\Shop\Product\IndexPageInterface;
19
use Sylius\Behat\Page\Shop\Product\ShowPageInterface;
20
use Sylius\Behat\Page\Shop\ProductReview\IndexPageInterface as ProductReviewIndexPageInterface;
21
use Sylius\Component\Core\Model\ProductInterface;
22
use Sylius\Component\Core\Model\TaxonInterface;
23
use Webmozart\Assert\Assert;
24
25
/**
26
 * @author Kamil Kokot <[email protected]>
27
 * @author Magdalena Banasiak <[email protected]>
28
 * @author Anna Walasek <[email protected]>
29
 */
30
final class ProductContext implements Context
31
{
32
    /**
33
     * @var ShowPageInterface
34
     */
35
    private $showPage;
36
37
    /**
38
     * @var IndexPageInterface
39
     */
40
    private $indexPage;
41
42
    /**
43
     * @var ProductReviewIndexPageInterface
44
     */
45
    private $productReviewsIndexPage;
46
47
    /**
48
     * @param ShowPageInterface $showPage
49
     * @param IndexPageInterface $indexPage
50
     * @param ProductReviewIndexPageInterface $productReviewsIndexPage
51
     */
52
    public function __construct(
53
        ShowPageInterface $showPage,
54
        IndexPageInterface $indexPage,
55
        ProductReviewIndexPageInterface $productReviewsIndexPage
56
    ) {
57
        $this->showPage = $showPage;
58
        $this->indexPage = $indexPage;
59
        $this->productReviewsIndexPage = $productReviewsIndexPage;
60
    }
61
62
    /**
63
     * @Then I should be able to access product :product
64
     */
65
    public function iShouldBeAbleToAccessProduct(ProductInterface $product)
66
    {
67
        $this->showPage->tryToOpen(['slug' => $product->getSlug()]);
68
69
        Assert::true($this->showPage->isOpen(['slug' => $product->getSlug()]));
70
    }
71
72
    /**
73
     * @Then I should not be able to access product :product
74
     */
75
    public function iShouldNotBeAbleToAccessProduct(ProductInterface $product)
76
    {
77
        $this->showPage->tryToOpen(['slug' => $product->getSlug()]);
78
79
        Assert::false($this->showPage->isOpen(['slug' => $product->getSlug()]));
80
    }
81
82
    /**
83
     * @When /^I check (this product)'s details$/
84
     * @When /^I check (this product)'s details in the ("([^"]+)" locale)$/
85
     * @When I view product :product
86
     * @When I view product :product in the :localeCode locale
87
     */
88
    public function iOpenProductPage(ProductInterface $product, $localeCode = 'en_US')
89
    {
90
        $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...
91
    }
92
93
    /**
94
     * @When /^I try to check (this product)'s details in the ("([^"]+)" locale)$/
95
     */
96
    public function iTryToOpenProductPage(ProductInterface $product, $localeCode = 'en_US')
97
    {
98
        $this->showPage->tryToOpen([
99
            '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...
100
            '_locale' => $localeCode,
101
        ]);
102
    }
103
104
    /**
105
     * @Then /^I should not be able to view (this product) in the ("([^"]+)" locale)$/
106
     */
107
    public function iShouldNotBeAbleToViewThisProductInLocale(ProductInterface $product, $localeCode = 'en_US')
108
    {
109
        Assert::false(
110
            $this->showPage->isOpen([
111
                '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...
112
                '_locale' => $localeCode,
113
            ])
114
        );
115
    }
116
117
    /**
118
     * @Then I should see the product name :name
119
     */
120
    public function iShouldSeeProductName($name)
121
    {
122
        Assert::same($this->showPage->getName(), $name);
123
    }
124
125
    /**
126
     * @When I open page :url
127
     */
128
    public function iOpenPage($url)
129
    {
130
        $this->showPage->visit($url);
131
    }
132
133
    /**
134
     * @Then I should be on :product product detailed page
135
     * @Then I should still be on product :product page
136
     */
137
    public function iShouldBeOnProductDetailedPage(ProductInterface $product)
138
    {
139
        Assert::true($this->showPage->isOpen(['slug' => $product->getSlug()]));
140
    }
141
142
    /**
143
     * @Then I should (also) see the product attribute :attributeName with value :expectedAttribute
144
     */
145
    public function iShouldSeeTheProductAttributeWithValue($attributeName, $expectedAttribute)
146
    {
147
        Assert::same($this->showPage->getAttributeByName($attributeName), $expectedAttribute);
148
    }
149
150
    /**
151
     * @Then I should not see the product attribute :attributeName
152
     */
153
    public function iShouldNotSeeTheProductAttribute(string $attributeName): void
154
    {
155
        $this->showPage->getAttributeByName($attributeName);
156
    }
157
158
    /**
159
     * @Then I should (also) see the product attribute :attributeName with date :expectedAttribute
160
     */
161
    public function iShouldSeeTheProductAttributeWithDate($attributeName, $expectedAttribute)
162
    {
163
        Assert::eq(
164
            new \DateTime($this->showPage->getAttributeByName($attributeName)),
165
            new \DateTime($expectedAttribute)
166
        );
167
    }
168
169
    /**
170
     * @Then I should see :count attributes
171
     */
172
    public function iShouldSeeAttributes($count)
173
    {
174
        Assert::same(count($this->getProductAttributes()), (int) $count);
175
    }
176
177
    /**
178
     * @Then the first attribute should be :name
179
     */
180
    public function theFirstAttributeShouldBe($name)
181
    {
182
        $attributes = $this->getProductAttributes();
183
184
        Assert::same(reset($attributes)->getText(), $name);
185
    }
186
187
    /**
188
     * @Then the last attribute should be :name
189
     */
190
    public function theLastAttributeShouldBe($name)
191
    {
192
        $attributes = $this->getProductAttributes();
193
194
        Assert::same(end($attributes)->getText(), $name);
195
    }
196
197
    /**
198
     * @When /^I browse products from (taxon "([^"]+)")$/
199
     */
200
    public function iCheckListOfProductsForTaxon(TaxonInterface $taxon)
201
    {
202
        $this->indexPage->open(['slug' => $taxon->getSlug()]);
203
    }
204
205
    /**
206
     * @When I search for products with name :name
207
     */
208
    public function iSearchForProductsWithName($name)
209
    {
210
        $this->indexPage->search($name);
211
    }
212
213
    /**
214
     * @When I sort products by the lowest price first
215
     */
216
    public function iSortProductsByTheLowestPriceFirst()
217
    {
218
        $this->indexPage->sort('Cheapest first');
219
    }
220
221
    /**
222
     * @When I sort products by the highest price first
223
     */
224
    public function iSortProductsByTheHighestPriceFisrt()
225
    {
226
        $this->indexPage->sort('Most expensive first');
227
    }
228
229
    /**
230
     * @When I sort products alphabetically from a to z
231
     */
232
    public function iSortProductsAlphabeticallyFromAToZ()
233
    {
234
        $this->indexPage->sort('From A to Z');
235
    }
236
237
    /**
238
     * @When I sort products alphabetically from z to a
239
     */
240
    public function iSortProductsAlphabeticallyFromZToA()
241
    {
242
        $this->indexPage->sort('From Z to A');
243
    }
244
245
    /**
246
     * @When I clear filter
247
     */
248
    public function iClearFilter()
249
    {
250
        $this->indexPage->clearFilter();
251
    }
252
253
    /**
254
     * @Then I should see the product :productName
255
     */
256
    public function iShouldSeeProduct($productName)
257
    {
258
        Assert::true($this->indexPage->isProductOnList($productName));
259
    }
260
261
    /**
262
     * @Then I should not see the product :productName
263
     */
264
    public function iShouldNotSeeProduct($productName)
265
    {
266
        Assert::false($this->indexPage->isProductOnList($productName));
267
    }
268
269
    /**
270
     * @Then I should see empty list of products
271
     */
272
    public function iShouldSeeEmptyListOfProducts()
273
    {
274
        Assert::true($this->indexPage->isEmpty());
275
    }
276
277
    /**
278
     * @Then I should see that it is out of stock
279
     */
280
    public function iShouldSeeItIsOutOfStock()
281
    {
282
        Assert::true($this->showPage->isOutOfStock());
283
    }
284
285
    /**
286
     * @Then I should be unable to add it to the cart
287
     */
288
    public function iShouldBeUnableToAddItToTheCart()
289
    {
290
        Assert::false($this->showPage->hasAddToCartButton());
291
    }
292
293
    /**
294
     * @Then the product price should be :price
295
     * @Then I should see the product price :price
296
     */
297
    public function iShouldSeeTheProductPrice($price)
298
    {
299
        Assert::same($this->showPage->getPrice(), $price);
300
    }
301
302
    /**
303
     * @When I set its :optionName to :optionValue
304
     */
305
    public function iSetItsOptionTo($optionName, $optionValue)
306
    {
307
        $this->showPage->selectOption($optionName, $optionValue);
308
    }
309
310
    /**
311
     * @When I select :variantName variant
312
     */
313
    public function iSelectVariant($variantName)
314
    {
315
        $this->showPage->selectVariant($variantName);
316
    }
317
318
    /**
319
     * @Then its current variant should be named :name
320
     */
321
    public function itsCurrentVariantShouldBeNamed($name)
322
    {
323
        Assert::same($this->showPage->getCurrentVariantName(), $name);
324
    }
325
326
    /**
327
     * @Then I should see the product :productName with price :productPrice
328
     */
329
    public function iShouldSeeTheProductWithPrice($productName, $productPrice)
330
    {
331
        Assert::same($this->indexPage->getProductPrice($productName), $productPrice);
332
    }
333
334
    /**
335
     * @Then /^I should be notified that (this product) does not have sufficient stock$/
336
     */
337
    public function iShouldBeNotifiedThatThisProductDoesNotHaveSufficientStock(ProductInterface $product)
338
    {
339
        Assert::true($this->showPage->hasProductOutOfStockValidationMessage($product));
340
    }
341
342
    /**
343
     * @Then /^I should not be notified that (this product) does not have sufficient stock$/
344
     */
345
    public function iShouldNotBeNotifiedThatThisProductDoesNotHaveSufficientStock(ProductInterface $product)
346
    {
347
        Assert::false($this->showPage->hasProductOutOfStockValidationMessage($product));
348
    }
349
350
    /**
351
     * @Then I should see a main image
352
     */
353
    public function iShouldSeeAMainImage()
354
    {
355
        Assert::true($this->showPage->isMainImageDisplayed());
356
    }
357
358
    /**
359
     * @When /^I view (oldest|newest) products from (taxon "([^"]+)")$/
360
     */
361
    public function iViewSortedProductsFromTaxon($sortDirection, TaxonInterface $taxon)
362
    {
363
        $sorting = ['createdAt' => 'oldest' === $sortDirection ? 'asc' : 'desc'];
364
365
        $this->indexPage->open(['slug' => $taxon->getSlug(), 'sorting' => $sorting]);
366
    }
367
368
    /**
369
     * @Then I should see :numberOfProducts products in the list
370
     */
371
    public function iShouldSeeProductsInTheList($numberOfProducts)
372
    {
373
        Assert::same($this->indexPage->countProductsItems(), (int) $numberOfProducts);
374
    }
375
376
    /**
377
     * @Then I should see a product with name :name
378
     */
379
    public function iShouldSeeProductWithName($name)
380
    {
381
        Assert::true($this->indexPage->isProductOnPageWithName($name));
382
    }
383
384
    /**
385
     * @Then the first product on the list should have name :name
386
     */
387
    public function theFirstProductOnTheListShouldHaveName($name)
388
    {
389
        Assert::same($this->indexPage->getFirstProductNameFromList(), $name);
390
    }
391
392
    /**
393
     * @Then the first product on the list should have name :name and price :price
394
     */
395
    public function theFirstProductOnTheListShouldHaveNameAndPrice($name, $price)
396
    {
397
        Assert::same($this->indexPage->getFirstProductNameFromList(), $name);
398
        Assert::same($this->indexPage->getProductPrice($name), $price);
399
    }
400
401
    /**
402
     * @Then the last product on the list should have name :name
403
     */
404
    public function theLastProductOnTheListShouldHaveName($name)
405
    {
406
        Assert::same($this->indexPage->getLastProductNameFromList(), $name);
407
    }
408
409
    /**
410
     * @Then the last product on the list should have name :name and price :price
411
     */
412
    public function theLastProductOnTheListShouldHaveNameAndPrice($name, $price)
413
    {
414
        Assert::same($this->indexPage->getLastProductNameFromList(), $name);
415
        Assert::same($this->indexPage->getProductPrice($name), $price);
416
    }
417
418
    /**
419
     * @Then I should see :count product reviews
420
     */
421
    public function iShouldSeeProductReviews($count)
422
    {
423
        Assert::same($this->showPage->countReviews(), (int) $count);
424
    }
425
426
    /**
427
     * @Then I should see reviews titled :firstReview, :secondReview and :thirdReview
428
     */
429
    public function iShouldSeeReviewsTitled(...$reviews)
430
    {
431
        foreach ($reviews as $review) {
432
            Assert::true(
433
                $this->showPage->hasReviewTitled($review),
434
                sprintf('Product should have review titled "%s" but it does not.', $review)
435
            );
436
        }
437
    }
438
439
    /**
440
     * @Then I should not see review titled :title
441
     */
442
    public function iShouldNotSeeReviewTitled($title)
443
    {
444
        Assert::false($this->showPage->hasReviewTitled($title));
445
    }
446
447
    /**
448
     * @When /^I check (this product)'s reviews$/
449
     */
450
    public function iCheckThisProductSReviews(ProductInterface $product)
451
    {
452
        $this->productReviewsIndexPage->open(['slug' => $product->getSlug()]);
453
    }
454
455
    /**
456
     * @Then /^I should see (\d+) product reviews in the list$/
457
     */
458
    public function iShouldSeeNumberOfProductReviewsInTheList($count)
459
    {
460
        Assert::same($this->productReviewsIndexPage->countReviews(), (int) $count);
461
    }
462
463
    /**
464
     * @Then I should not see review titled :title in the list
465
     */
466
    public function iShouldNotSeeReviewTitledInTheList($title)
467
    {
468
        Assert::false($this->productReviewsIndexPage->hasReviewTitled($title));
469
    }
470
471
    /**
472
     * @Then /^I should be notified that there are no reviews$/
473
     */
474
    public function iShouldBeNotifiedThatThereAreNoReviews()
475
    {
476
        Assert::true($this->productReviewsIndexPage->hasNoReviewsMessage());
477
    }
478
479
    /**
480
     * @Then I should see :rating as its average rating
481
     */
482
    public function iShouldSeeAsItsAverageRating($rating)
483
    {
484
        Assert::same($this->showPage->getAverageRating(), (float) $rating);
485
    }
486
487
    /**
488
     * @Then /^I should(?:| also) see the product association "([^"]+)" with (products "[^"]+" and "[^"]+")$/
489
     */
490
    public function iShouldSeeTheProductAssociationWithProducts($productAssociationName, array $products)
491
    {
492
        Assert::true(
493
            $this->showPage->hasAssociation($productAssociationName),
494
            sprintf('There should be an association named "%s" but it does not.', $productAssociationName)
495
        );
496
497
        foreach ($products as $product) {
498
            $this->assertProductIsInAssociation($product->getName(), $productAssociationName);
499
        }
500
    }
501
502
    /**
503
     * @Then /^average rating of (product "[^"]+") should be (\d+)$/
504
     */
505
    public function thisProductAverageRatingShouldBe(ProductInterface $product, $averageRating)
506
    {
507
        $this->showPage->tryToOpen(['slug' => $product->getSlug()]);
508
        $this->iShouldSeeAsItsAverageRating($averageRating);
509
    }
510
511
    /**
512
     * @Then they should have order like :firstProductName, :secondProductName and :thirdProductName
513
     */
514
    public function theyShouldHaveOrderLikeAnd(...$productNames)
515
    {
516
        Assert::true($this->indexPage->hasProductsInOrder($productNames));
517
    }
518
519
    /**
520
     * @param string $productName
521
     * @param string $productAssociationName
522
     *
523
     * @throws \InvalidArgumentException
524
     */
525
    private function assertProductIsInAssociation($productName, $productAssociationName)
526
    {
527
        Assert::true(
528
            $this->showPage->hasProductInAssociation($productName, $productAssociationName),
529
            sprintf(
530
                'There should be an associated product "%s" under association "%s" but it does not.',
531
                $productName,
532
                $productAssociationName
533
            )
534
        );
535
    }
536
537
    /**
538
     * @return NodeElement[]
539
     *
540
     * @throws \InvalidArgumentException
541
     */
542
    private function getProductAttributes()
543
    {
544
        $attributes = $this->showPage->getAttributes();
545
        Assert::notNull($attributes, 'The product has no attributes.');
546
547
        return $attributes;
548
    }
549
}
550