Completed
Push — symfony3-fqcn-sylius-2 ( 026c63...c92ad2 )
by Kamil
31:21 queued 13:20
created

ManagingProductsContext::resolveCurrentPage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 8
nc 1
nop 0
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\Admin;
13
14
use Behat\Behat\Context\Context;
15
use Sylius\Behat\NotificationType;
16
use Sylius\Behat\Page\Admin\Crud\CreatePageInterface;
17
use Sylius\Behat\Page\Admin\Crud\UpdatePageInterface;
18
use Sylius\Behat\Page\Admin\Product\CreateConfigurableProductPageInterface;
19
use Sylius\Behat\Page\Admin\Product\CreateSimpleProductPageInterface;
20
use Sylius\Behat\Page\Admin\Product\IndexPageInterface;
21
use Sylius\Behat\Page\Admin\Product\IndexPerTaxonPageInterface;
22
use Sylius\Behat\Page\Admin\Product\UpdateConfigurableProductPageInterface;
23
use Sylius\Behat\Page\Admin\Product\UpdateSimpleProductPageInterface;
24
use Sylius\Behat\Page\Admin\ProductReview\IndexPageInterface as ProductReviewIndexPageInterface;
25
use Sylius\Behat\Page\SymfonyPageInterface;
26
use Sylius\Behat\Service\NotificationCheckerInterface;
27
use Sylius\Behat\Service\Resolver\CurrentPageResolverInterface;
28
use Sylius\Component\Core\Model\ChannelInterface;
29
use Sylius\Component\Core\Model\ProductInterface;
30
use Sylius\Behat\Service\SharedStorageInterface;
31
use Sylius\Component\Currency\Model\CurrencyInterface;
32
use Sylius\Component\Product\Model\ProductAssociationTypeInterface;
33
use Sylius\Component\Taxonomy\Model\TaxonInterface;
34
use Webmozart\Assert\Assert;
35
36
/**
37
 * @author Kamil Kokot <[email protected]>
38
 * @author Magdalena Banasiak <[email protected]>
39
 * @author Łukasz Chruściel <[email protected]>
40
 */
41
final class ManagingProductsContext implements Context
42
{
43
    /**
44
     * @var SharedStorageInterface
45
     */
46
    private $sharedStorage;
47
48
    /**x
49
     * @var CreateSimpleProductPageInterface
50
     */
51
    private $createSimpleProductPage;
52
53
    /**
54
     * @var CreateConfigurableProductPageInterface
55
     */
56
    private $createConfigurableProductPage;
57
58
    /**
59
     * @var IndexPageInterface
60
     */
61
    private $indexPage;
62
63
    /**
64
     * @var UpdateSimpleProductPageInterface
65
     */
66
    private $updateSimpleProductPage;
67
68
    /**
69
     * @var UpdateConfigurableProductPageInterface
70
     */
71
    private $updateConfigurableProductPage;
72
73
    /**
74
     * @var ProductReviewIndexPageInterface
75
     */
76
    private $productReviewIndexPage;
77
78
    /**
79
     * @var IndexPerTaxonPageInterface
80
     */
81
    private $indexPerTaxonPage;
82
83
    /**
84
     * @var CurrentPageResolverInterface
85
     */
86
    private $currentPageResolver;
87
88
    /**
89
     * @var NotificationCheckerInterface
90
     */
91
    private $notificationChecker;
92
93
    /**
94
     * @param SharedStorageInterface $sharedStorage
95
     * @param CreateSimpleProductPageInterface $createSimpleProductPage
96
     * @param CreateConfigurableProductPageInterface $createConfigurableProductPage
97
     * @param IndexPageInterface $indexPage
98
     * @param UpdateSimpleProductPageInterface $updateSimpleProductPage
99
     * @param UpdateConfigurableProductPageInterface $updateConfigurableProductPage
100
     * @param ProductReviewIndexPageInterface $productReviewIndexPage
101
     * @param IndexPerTaxonPageInterface $indexPerTaxonPage
102
     * @param CurrentPageResolverInterface $currentPageResolver
103
     * @param NotificationCheckerInterface $notificationChecker
104
     */
105
    public function __construct(
106
        SharedStorageInterface $sharedStorage,
107
        CreateSimpleProductPageInterface $createSimpleProductPage,
108
        CreateConfigurableProductPageInterface $createConfigurableProductPage,
109
        IndexPageInterface $indexPage,
110
        UpdateSimpleProductPageInterface $updateSimpleProductPage,
111
        UpdateConfigurableProductPageInterface $updateConfigurableProductPage,
112
        ProductReviewIndexPageInterface $productReviewIndexPage,
113
        IndexPerTaxonPageInterface $indexPerTaxonPage,
114
        CurrentPageResolverInterface $currentPageResolver,
115
        NotificationCheckerInterface $notificationChecker
116
    ) {
117
        $this->sharedStorage = $sharedStorage;
118
        $this->createSimpleProductPage = $createSimpleProductPage;
119
        $this->createConfigurableProductPage = $createConfigurableProductPage;
120
        $this->indexPage = $indexPage;
121
        $this->updateSimpleProductPage = $updateSimpleProductPage;
122
        $this->updateConfigurableProductPage = $updateConfigurableProductPage;
123
        $this->productReviewIndexPage = $productReviewIndexPage;
124
        $this->indexPerTaxonPage = $indexPerTaxonPage;
125
        $this->currentPageResolver = $currentPageResolver;
126
        $this->notificationChecker = $notificationChecker;
127
    }
128
129
    /**
130
     * @Given I want to create a new simple product
131
     */
132
    public function iWantToCreateANewSimpleProduct()
133
    {
134
        $this->createSimpleProductPage->open();
135
    }
136
137
    /**
138
     * @Given I want to create a new configurable product
139
     */
140
    public function iWantToCreateANewConfigurableProduct()
141
    {
142
        $this->createConfigurableProductPage->open();
143
    }
144
145
    /**
146
     * @When I specify its code as :code
147
     * @When I do not specify its code
148
     */
149
    public function iSpecifyItsCodeAs($code = null)
150
    {
151
        $currentPage = $this->resolveCurrentPage();
152
153
        $currentPage->specifyCode($code);
154
    }
155
156
    /**
157
     * @When I name it :name in :language
158
     * @When I rename it to :name in :language
159
     */
160
    public function iRenameItToIn($name, $language)
161
    {
162
        $currentPage = $this->resolveCurrentPage();
163
164
        $currentPage->nameItIn($name, $language);
165
    }
166
167
    /**
168
     * @When I add it
169
     * @When I try to add it
170
     */
171
    public function iAddIt()
172
    {
173
        /** @var CreatePageInterface $currentPage */
174
        $currentPage = $this->resolveCurrentPage();
175
176
        $currentPage->create();
177
    }
178
179
    /**
180
     * @When I disable its inventory tracking
181
     */
182
    public function iDisableItsTracking()
183
    {
184
        $this->updateSimpleProductPage->disableTracking();
185
    }
186
187
    /**
188
     * @When I enable its inventory tracking
189
     */
190
    public function iEnableItsTracking()
191
    {
192
        $this->updateSimpleProductPage->enableTracking();
193
    }
194
195
    /**
196
     * @When /^I set its(?:| default) price to ("(?:€|£|\$)[^"]+")$/
197
     */
198
    public function iSetItsPriceTo($price)
199
    {
200
        $this->createSimpleProductPage->specifyPrice($price);
201
    }
202
203
    /**
204
     * @When I choose :calculatorName calculator
205
     */
206
    public function iChooseCalculator($calculatorName)
207
    {
208
        $this->createSimpleProductPage->choosePricingCalculator($calculatorName);
209
    }
210
211
    /**
212
     * @When /^I set its price to "(?:€|£|\$)([^"]+)" for ("[^"]+" currency) and ("[^"]+" channel)$/
213
     */
214
    public function iSetItsPriceToForCurrencyAndChannel($price, CurrencyInterface $currency, ChannelInterface $channel)
215
    {
216
        $this->createSimpleProductPage->specifyPriceForChannelAndCurrency($price, $channel, $currency);
217
    }
218
219
    /**
220
     * @When I set its slug to :slug
221
     * @When I set its slug to :slug in :language
222
     * @When I remove its slug
223
     */
224
    public function iSetItsSlugToIn($slug = null, $language = 'en_US')
225
    {
226
        $this->createSimpleProductPage->specifySlugIn($slug, $language);
227
    }
228
229
    /**
230
     * @When I enable slug modification
231
     * @When I enable slug modification in :localeCode
232
     */
233
    public function iEnableSlugModification($localeCode = 'en_US')
234
    {
235
        $this->updateSimpleProductPage->activateLanguageTab($localeCode);
236
        $this->updateSimpleProductPage->enableSlugModification($localeCode);
237
    }
238
239
    /**
240
     * @Then the product :productName should appear in the store
241
     * @Then the product :productName should be in the shop
242
     * @Then this product should still be named :productName
243
     */
244
    public function theProductShouldAppearInTheShop($productName)
245
    {
246
        $this->iWantToBrowseProducts();
247
248
        Assert::true(
249
            $this->indexPage->isSingleResourceOnPage(['name' => $productName]),
250
            sprintf('The product with name %s has not been found.', $productName)
251
        );
252
    }
253
254
    /**
255
     * @Given I am browsing products
256
     * @When I want to browse products
257
     */
258
    public function iWantToBrowseProducts()
259
    {
260
        $this->indexPage->open();
261
    }
262
263
    /**
264
     * @When /^I am browsing products from ("([^"]+)" taxon)$/
265
     */
266
    public function iAmBrowsingProductsFromTaxon(TaxonInterface $taxon)
267
    {
268
        $this->indexPerTaxonPage->open(['taxonId' => $taxon->getId()]);
269
    }
270
271
    /**
272
     * @When I filter them by :taxonName taxon
273
     */
274
    public function iFilterThemByTaxon($taxonName)
275
    {
276
        $this->indexPage->filterByTaxon($taxonName);
277
    }
278
279
    /**
280
     * @Then I should( still) see a product with :field :value
281
     */
282
    public function iShouldSeeProductWith($field, $value)
283
    {
284
        Assert::true(
285
            $this->indexPage->isSingleResourceOnPage([$field => $value]),
286
            sprintf('The product with %s "%s" has not been found.', $field, $value)
287
        );
288
    }
289
290
    /**
291
     * @Then I should not see any product with :field :value
292
     */
293
    public function iShouldNotSeeAnyProductWith($field, $value)
294
    {
295
        Assert::false(
296
            $this->indexPage->isSingleResourceOnPage([$field => $value]),
297
            sprintf('The product with %s "%s" has been found.', $field, $value)
298
        );
299
    }
300
301
    /**
302
     * @Then the first product on the list should have :field :value
303
     */
304
    public function theFirstProductOnTheListShouldHave($field, $value)
305
    {
306
        $currentPage = $this->resolveCurrentPage();
307
308
        $actualValue = $currentPage->getColumnFields($field)[0];
309
310
        Assert::same(
311
            $actualValue,
312
            $value,
313
            sprintf('Expected first product\'s %s to be "%s", but it is "%s".', $field, $value, $actualValue)
314
        );
315
    }
316
317
    /**
318
     * @Then the last product on the list should have :field :value
319
     */
320
    public function theLastProductOnTheListShouldHave($field, $value)
321
    {
322
        $columnFields = $this->indexPerTaxonPage->getColumnFields($field);
323
        $actualValue = end($columnFields);
324
325
        Assert::same(
326
            $actualValue,
327
            $value,
328
            sprintf('Expected last product\'s %s to be "%s", but it is "%s".', $field, $value, $actualValue)
329
        );
330
    }
331
332
    /**
333
     * @When I switch the way products are sorted by :field
334
     * @When I start sorting products by :field
335
     * @Given the products are already sorted by :field
336
     */
337
    public function iSortProductsBy($field)
338
    {
339
        $this->indexPage->sortBy($field);
340
    }
341
342
    /**
343
     * @Then I should see :numberOfProducts products in the list
344
     */
345
    public function iShouldSeeProductsInTheList($numberOfProducts)
346
    {
347
        $foundRows = $this->indexPage->countItems();
348
349
        Assert::same(
350
            (int) $numberOfProducts,
351
            $foundRows,
352
            '%s rows with products should appear on page, %s rows has been found'
353
        );
354
    }
355
356
    /**
357
     * @When I delete the :product product
358
     * @When I try to delete the :product product
359
     */
360
    public function iDeleteProduct(ProductInterface $product)
361
    {
362
        $this->sharedStorage->set('product', $product);
363
364
        $this->iWantToBrowseProducts();
365
        $this->indexPage->deleteResourceOnPage(['name' => $product->getName()]);
366
    }
367
368
    /**
369
     * @Then /^(this product) should not exist in the product catalog$/
370
     */
371
    public function productShouldNotExist(ProductInterface $product)
372
    {
373
        $this->iWantToBrowseProducts();
374
375
        Assert::false(
376
            $this->indexPage->isSingleResourceOnPage(['code' => $product->getCode()]),
377
            sprintf('Product with code %s exists but should not.', $product->getCode())
378
        );
379
    }
380
381
    /**
382
     * @Then I should be notified that this product is in use and cannot be deleted
383
     */
384
    public function iShouldBeNotifiedOfFailure()
385
    {
386
        $this->notificationChecker->checkNotification(
387
            "Cannot delete, the product is in use.",
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal Cannot delete, the product is in use. does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
388
            NotificationType::failure()
389
        );
390
    }
391
392
    /**
393
     * @Then /^(this product) should still exist in the product catalog$/
394
     */
395
    public function productShouldExistInTheProductCatalog(ProductInterface $product)
396
    {
397
        $this->theProductShouldAppearInTheShop($product->getName());
398
    }
399
400
    /**
401
     * @When I want to modify the :product product
402
     * @When /^I want to modify (this product)$/
403
     */
404
    public function iWantToModifyAProduct(ProductInterface $product)
405
    {
406
        $this->sharedStorage->set('product', $product);
407
408
        if ($product->isSimple()) {
409
            $this->updateSimpleProductPage->open(['id' => $product->getId()]);
410
            return;
411
        }
412
413
        $this->updateConfigurableProductPage->open(['id' => $product->getId()]);
414
    }
415
416
    /**
417
     * @Then the code field should be disabled
418
     */
419
    public function theCodeFieldShouldBeDisabled()
420
    {
421
        $currentPage = $this->resolveCurrentPage();
422
423
        Assert::true(
424
            $currentPage->isCodeDisabled(),
425
            'Code should be immutable, but it does not.'
426
        );
427
    }
428
429
    /**
430
     * @Then the slug field should not be editable
431
     * @Then the slug field in :localeCode (also )should not be editable
432
     */
433
    public function theSlugFieldShouldNotBeEditable($localeCode = 'en_US')
434
    {
435
        Assert::true(
436
            $this->updateSimpleProductPage->isSlugReadOnlyIn($localeCode),
437
            'Slug should be immutable, but it does not.'
438
        );
439
    }
440
441
    /**
442
     * @Then /^this product price should be "(?:€|£|\$)([^"]+)"$/
443
     */
444
    public function thisProductPriceShouldBeEqualTo($price)
445
    {
446
        $this->assertElementValue('price', $price);
447
    }
448
449
    /**
450
     * @Then this product name should be :name
451
     */
452
    public function thisProductElementShouldBe($name)
453
    {
454
        $this->assertElementValue('name', $name);
455
    }
456
457
    /**
458
     * @Then /^I should be notified that (code|name|slug) is required$/
459
     */
460
    public function iShouldBeNotifiedThatIsRequired($element)
461
    {
462
        $this->assertValidationMessage($element, sprintf('Please enter product %s.', $element));
463
    }
464
465
    /**
466
     * @Then I should be notified that price is required
467
     */
468
    public function iShouldBeNotifiedThatPriceIsRequired()
469
    {
470
        $this->assertValidationMessage('price', 'Please enter the price.');
471
    }
472
473
    /**
474
     * @When I save my changes
475
     * @When I try to save my changes
476
     */
477
    public function iSaveMyChanges()
478
    {
479
        /** @var UpdatePageInterface $currentPage */
480
        $currentPage = $this->resolveCurrentPage();
481
482
        $currentPage->saveChanges();
483
    }
484
485
    /**
486
     * @When /^I change its price to "(?:€|£|\$)([^"]+)"$/
487
     */
488
    public function iChangeItsPriceTo($price)
489
    {
490
        $this->updateSimpleProductPage->specifyPrice($price);
491
    }
492
493
    /**
494
     * @Given I add the :optionName option to it
495
     */
496
    public function iAddTheOptionToIt($optionName)
497
    {
498
        $this->createConfigurableProductPage->selectOption($optionName);
499
    }
500
501
    /**
502
     * @When I set its :attribute attribute to :value
503
     */
504
    public function iSetItsAttributeTo($attribute, $value)
505
    {
506
        $this->createSimpleProductPage->addAttribute($attribute, $value);
507
    }
508
509
    /**
510
     * @When I remove its :attribute attribute
511
     */
512
    public function iRemoveItsAttribute($attribute)
513
    {
514
        $this->createSimpleProductPage->removeAttribute($attribute);
515
    }
516
517
    /**
518
     * @Then /^attribute "([^"]+)" of (product "[^"]+") should be "([^"]+)"$/
519
     */
520
    public function itsAttributeShouldBe($attribute, ProductInterface $product, $value)
521
    {
522
        $this->updateSimpleProductPage->open(['id' => $product->getId()]);
523
524
        Assert::same(
525
            $value,
526
            $this->updateSimpleProductPage->getAttributeValue($attribute),
527
            sprintf('ProductAttribute "%s" should have value "%s" but it does not.', $attribute, $value)
528
        );
529
    }
530
531
    /**
532
     * @Then /^(product "[^"]+") should not have a "([^"]+)" attribute$/
533
     */
534
    public function productShouldNotHaveAttribute(ProductInterface $product, $attribute)
535
    {
536
        $this->updateSimpleProductPage->open(['id' => $product->getId()]);
537
538
        Assert::false(
539
            $this->updateSimpleProductPage->hasAttribute($attribute),
540
            sprintf('Product "%s" should not have attribute "%s" but it does.', $product->getName(), $attribute)
541
        );
542
    }
543
544
    /**
545
     * @Given product with :element :value should not be added
546
     */
547
    public function productWithNameShouldNotBeAdded($element, $value)
548
    {
549
        $this->iWantToBrowseProducts();
550
551
        Assert::false(
552
            $this->indexPage->isSingleResourceOnPage([$element => $value]),
553
            sprintf('Product with %s %s was created, but it should not.', $element, $value)
554
        );
555
    }
556
557
    /**
558
     * @When I remove its name from :language translation
559
     */
560
    public function iRemoveItsNameFromTranslation($language)
561
    {
562
        $currentPage = $this->resolveCurrentPage();
563
564
        $currentPage->nameItIn('', $language);
565
    }
566
567
    /**
568
     * @Then /^this product should have (?:a|an) "([^"]+)" option$/
569
     */
570
    public function thisProductShouldHaveOption($productOption)
571
    {
572
        $this->updateConfigurableProductPage->isProductOptionChosen($productOption);
573
    }
574
575
    /**
576
     * @Then the option field should be disabled
577
     */
578
    public function theOptionFieldShouldBeDisabled()
579
    {
580
        Assert::true(
581
            $this->updateConfigurableProductPage->isProductOptionsDisabled(),
582
            'Options field should be immutable, but it does not.'
583
        );
584
    }
585
586
    /**
587
     * @When /^I choose main (taxon "([^"]+)")$/
588
     */
589
    public function iChooseMainTaxon(TaxonInterface $taxon)
590
    {
591
        $currentPage = $this->resolveCurrentPage();
592
593
        $currentPage->selectMainTaxon($taxon);
594
    }
595
596
    /**
597
     * @Then /^the slug of the ("[^"]+" product) should(?:| still) be "([^"]+)"$/
598
     * @Then /^the slug of the ("[^"]+" product) should(?:| still) be "([^"]+)" (in the "[^"]+" locale)$/
599
     */
600
    public function productSlugShouldBe(ProductInterface $product, $slug, $locale = "en_US")
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal en_US does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
601
    {
602
        $this->updateSimpleProductPage->open(['id' => $product->getId()]);
603
604
        Assert::same(
605
            $this->updateSimpleProductPage->getSlug($locale),
606
            $slug,
607
            'Expected slug %2$s, but found %s.'
608
        );
609
    }
610
611
    /**
612
     * @Then /^(this product) main taxon should be "([^"]+)"$/
613
     */
614
    public function thisProductMainTaxonShouldBe(ProductInterface $product, $taxonName)
615
    {
616
        /** @var UpdatePageInterface $currentPage */
617
        $currentPage = $this->resolveCurrentPage();
618
619
        $currentPage->open(['id' => $product->getId()]);
620
621
        Assert::true(
622
            $this->updateConfigurableProductPage->isMainTaxonChosen($taxonName),
623
            sprintf('The main taxon %s should be chosen, but it does not.', $taxonName)
624
        );
625
    }
626
627
    /**
628
     * @Then /^inventory of (this product) should not be tracked$/
629
     */
630
    public function thisProductShouldNotBeTracked(ProductInterface $product)
631
    {
632
        $this->iWantToModifyAProduct($product);
633
634
        Assert::false(
635
            $this->updateSimpleProductPage->isTracked(),
636
            '"%s" should not be tracked, but it is.'
637
        );
638
    }
639
640
    /**
641
     * @Then /^inventory of (this product) should be tracked$/
642
     */
643
    public function thisProductShouldBeTracked(ProductInterface $product)
644
    {
645
        $this->iWantToModifyAProduct($product);
646
647
        Assert::true(
648
            $this->updateSimpleProductPage->isTracked(),
649
            '"%s" should be tracked, but it is not.'
650
        );
651
    }
652
653
    /**
654
     * @When I attach the :path image with a code :code
655
     */
656
    public function iAttachImageWithACode($path, $code)
657
    {
658
        /** @var CreatePageInterface|UpdatePageInterface $currentPage */
659
        $currentPage = $this->resolveCurrentPage();
660
661
        $currentPage->attachImage($path, $code);
662
    }
663
664
    /**
665
     * @When I attach the :path image without a code
666
     */
667
    public function iAttachImageWithoutACode($path)
668
    {
669
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
670
        $currentPage = $this->resolveCurrentPage();
671
672
        $currentPage->attachImage($path);
673
    }
674
675
    /**
676
     * @When I associate as :productAssociationType the :productName product
677
     * @When I associate as :productAssociationType the :firstProductName and :secondProductName products
678
     */
679
    public function iAssociateProductsAsProductAssociation(
680
        ProductAssociationTypeInterface $productAssociationType,
681
        ...$productsNames
682
    ) {
683
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
684
        $currentPage = $this->resolveCurrentPage();
685
686
        $currentPage->associateProducts($productAssociationType, $productsNames);
0 ignored issues
show
Bug introduced by
The method associateProducts does only exist in Sylius\Behat\Page\Admin\...pleProductPageInterface, but not in Sylius\Behat\Page\Admin\...bleProductPageInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
687
    }
688
689
    /**
690
     * @When I remove an associated product :productName from :productAssociationType
691
     */
692
    public function iRemoveAnAssociatedProductFromProductAssociation(
693
        $productName,
694
        ProductAssociationTypeInterface $productAssociationType
695
    ) {
696
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
697
        $currentPage = $this->resolveCurrentPage();
698
699
        $currentPage->removeAssociatedProduct($productName, $productAssociationType);
0 ignored issues
show
Bug introduced by
The method removeAssociatedProduct does only exist in Sylius\Behat\Page\Admin\...pleProductPageInterface, but not in Sylius\Behat\Page\Admin\...bleProductPageInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
700
    }
701
702
    /**
703
     * @Then /^(this product) should have(?:| also) an image with a code "([^"]*)"$/
704
     * @Then /^the (product "[^"]+") should have(?:| also) an image with a code "([^"]*)"$/
705
     */
706
    public function thisProductShouldHaveAnImageWithCode(ProductInterface $product, $code)
707
    {
708
        $this->sharedStorage->set('product', $product);
709
710
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
711
        $currentPage = $this->resolveCurrentPage();
712
713
        Assert::true(
714
            $currentPage->isImageWithCodeDisplayed($code),
715
            sprintf('Image with a code %s should have been displayed.', $code)
716
        );
717
    }
718
719
    /**
720
     * @Then /^(this product) should not have(?:| also) an image with a code "([^"]*)"$/
721
     */
722
    public function thisProductShouldNotHaveAnImageWithCode(ProductInterface $product, $code)
0 ignored issues
show
Unused Code introduced by
The parameter $product is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
723
    {
724
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
725
        $currentPage = $this->resolveCurrentPage();
726
727
        Assert::false(
728
            $currentPage->isImageWithCodeDisplayed($code),
729
            sprintf('Image with a code %s should not have been displayed.', $code)
730
        );
731
    }
732
733
    /**
734
     * @When I change the image with the :code code to :path
735
     */
736
    public function iChangeItsImageToPathForTheCode($path, $code)
737
    {
738
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
739
        $currentPage = $this->resolveCurrentPage();
740
741
        $currentPage->changeImageWithCode($code, $path);
742
    }
743
744
    /**
745
     * @When /^I remove(?:| also) an image with a code "([^"]*)"$/
746
     */
747
    public function iRemoveAnImageWithACode($code)
748
    {
749
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
750
        $currentPage = $this->resolveCurrentPage();
751
752
        $currentPage->removeImageWithCode($code);
753
    }
754
755
    /**
756
     * @When I remove the first image
757
     */
758
    public function iRemoveTheFirstImage()
759
    {
760
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
761
        $currentPage = $this->resolveCurrentPage();
762
763
        $currentPage->removeFirstImage();
764
    }
765
766
    /**
767
     * @Then /^(this product) should not have any images$/
768
     */
769
    public function thisProductShouldNotHaveImages(ProductInterface $product)
770
    {
771
        $this->iWantToModifyAProduct($product);
772
773
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
774
        $currentPage = $this->resolveCurrentPage();
775
776
        Assert::same(
777
            0,
778
            $currentPage->countImages(),
779
            'This product has %2$s, but it should not have.'
780
        );
781
    }
782
783
    /**
784
     * @Then the image code field should be disabled
785
     */
786
    public function theImageCodeFieldShouldBeDisabled()
787
    {
788
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
789
        $currentPage = $this->resolveCurrentPage();
790
791
        Assert::true(
792
            $currentPage->isImageCodeDisabled(),
793
            'Image code field should be disabled but it is not.'
794
        );
795
    }
796
797
    /**
798
     * @Then I should be notified that the image with this code already exists
799
     */
800
    public function iShouldBeNotifiedThatTheImageWithThisCodeAlreadyExists()
801
    {
802
        Assert::same($this->updateSimpleProductPage->getValidationMessageForImage('code'), 'Image code must be unique within this product.');
0 ignored issues
show
Unused Code introduced by
The call to UpdateSimpleProductPageI...dationMessageForImage() has too many arguments starting with 'code'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
803
    }
804
805
    /**
806
     * @Then I should be notified that an image code is required
807
     */
808
    public function iShouldBeNotifiedThatAnImageCodeIsRequired()
809
    {
810
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
811
        $currentPage = $this->resolveCurrentPage();
812
813
        Assert::same(
814
            $currentPage->getValidationMessageForImage(),
815
            'Please enter an image code.'
816
        );
817
    }
818
819
    /**
820
     * @Then there should still be only one image in the :product product
821
     */
822
    public function thereShouldStillBeOnlyOneImageInThisTaxon(ProductInterface $product)
823
    {
824
        $this->iWantToModifyAProduct($product);
825
826
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
827
        $currentPage = $this->resolveCurrentPage();
828
829
        Assert::same(
830
            1,
831
            $currentPage->countImages(),
832
            'This product has %2$s images, but it should have only one.'
833
        );
834
    }
835
836
    /**
837
     * @Then /^there should be no reviews of (this product)$/
838
     */
839
    public function thereAreNoProductReviews(ProductInterface $product)
840
    {
841
        $this->productReviewIndexPage->open();
842
843
        Assert::false(
844
            $this->productReviewIndexPage->isSingleResourceOnPage(['reviewSubject' => $product->getName()]),
845
            sprintf('There should be no reviews of %s.', $product->getName())
846
        );
847
    }
848
849
    /**
850
     * @Then /^the product for ("[^"]+" currency) and ("[^"]+" channel) should be priced at "(?:€|£|\$)([^"]+)"$/
851
     */
852
    public function theProductForCurrencyAndChannelShouldBePricedAt(CurrencyInterface $currency, ChannelInterface $channel, $price)
853
    {
854
        Assert::same(
855
            $this->updateSimpleProductPage->getPricingConfigurationForChannelAndCurrencyCalculator($channel, $currency),
856
            $price
857
        );
858
    }
859
860
    /**
861
     * @Then this product should( also) have an association :productAssociationType with product :productName
862
     * @Then this product should( also) have an association :productAssociationType with products :firstProductName and :secondProductName
863
     */
864
    public function theProductShouldHaveAnAssociationWithProducts(
865
        ProductAssociationTypeInterface $productAssociationType,
866
        ...$productsNames
867
    ) {
868
        foreach ($productsNames as $productName) {
869
            Assert::true(
870
                $this->updateSimpleProductPage->hasAssociatedProduct($productName, $productAssociationType),
871
                sprintf(
872
                    'This product should have an association %s with product %s, but it does not.',
873
                    $productAssociationType->getName(),
874
                    $productName
875
                )
876
            );
877
        }
878
    }
879
880
    /**
881
     * @Then this product should not have an association :productAssociationType with product :productName
882
     */
883
    public function theProductShouldNotHaveAnAssociationWithProducts(
884
        ProductAssociationTypeInterface $productAssociationType,
885
        $productName
886
    ) {
887
        Assert::false(
888
            $this->updateSimpleProductPage->hasAssociatedProduct($productName, $productAssociationType),
889
            sprintf(
890
                'This product should not have an association %s with product %s, but it does.',
891
                $productAssociationType->getName(),
892
                $productName
893
            )
894
        );
895
    }
896
897
    /**
898
     * @Then I should be notified that simple product code has to be unique
899
     */
900
    public function iShouldBeNotifiedThatSimpleProductCodeHasToBeUnique()
901
    {
902
        $this->assertValidationMessage('code', 'Simple product code must be unique among all products and product variants.');
903
    }
904
905
    /**
906
     * @Then I should be notified that code has to be unique
907
     */
908
    public function iShouldBeNotifiedThatCodeHasToBeUnique()
909
    {
910
        $this->assertValidationMessage('code', 'Product code must be unique.');
911
    }
912
913
    /**
914
     * @Then they should have order like :firstProductName, :secondProductName and :thirdProductName
915
     */
916
    public function theyShouldHaveOrderLikeAnd(...$productNames)
917
    {
918
        Assert::true(
919
            $this->indexPerTaxonPage->hasProductsInOrder($productNames),
920
            'The products have wrong order.'
921
        );
922
    }
923
924
    /**
925
     * @When I save my new configuration
926
     */
927
    public function iSaveMyNewConfiguration()
928
    {
929
        $this->indexPerTaxonPage->savePositions();
930
    }
931
932
    /**
933
     * @When I set the position of :productName to :position
934
     */
935
    public function iSetThePositionOfTo($productName, $position)
936
    {
937
        $this->indexPerTaxonPage->setPositionOfProduct($productName, (int) $position);
938
    }
939
940
    /**
941
     * @Then this product should( still) have slug :value in :language
942
     */
943
    public function thisProductElementShouldHaveSlugIn($slug, $language)
944
    {
945
        Assert::same(
946
            $this->updateSimpleProductPage->getSlug($language),
947
            $slug,
948
            'Expected slug %2$s, but found %s.'
949
        );
950
    }
951
952
    /**
953
     * @param string $element
954
     * @param string $value
955
     */
956
    private function assertElementValue($element, $value)
957
    {
958
        /** @var UpdatePageInterface $currentPage */
959
        $currentPage = $this->resolveCurrentPage();
960
961
        Assert::isInstanceOf($currentPage, UpdatePageInterface::class);
962
963
        Assert::true(
964
            $currentPage->hasResourceValues(
965
                [$element => $value]
966
            ),
967
            sprintf('Product should have %s with %s value.', $element, $value)
968
        );
969
    }
970
971
    /**
972
     * @param string $element
973
     * @param string $message
974
     */
975
    private function assertValidationMessage($element, $message)
976
    {
977
        /** @var CreatePageInterface|UpdatePageInterface $currentPage */
978
        $currentPage = $this->resolveCurrentPage();
979
980
        Assert::same($currentPage->getValidationMessage($element), $message);
981
    }
982
983
    /**
984
     * @return SymfonyPageInterface
985
     */
986
    private function resolveCurrentPage()
987
    {
988
        return $this->currentPageResolver->getCurrentPageWithForm([
989
            $this->indexPage,
990
            $this->indexPerTaxonPage,
991
            $this->createSimpleProductPage,
992
            $this->createConfigurableProductPage,
993
            $this->updateSimpleProductPage,
994
            $this->updateConfigurableProductPage,
995
        ]);
996
    }
997
}
998