Completed
Push — menu-builders ( ef8ea6...aa4c7a )
by Kamil
18:07
created

productShouldNotHaveAnyAttributes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 2
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 Behat\Mink\Exception\ElementNotFoundException;
16
use Sylius\Behat\NotificationType;
17
use Sylius\Behat\Page\Admin\Crud\CreatePageInterface;
18
use Sylius\Behat\Page\Admin\Crud\UpdatePageInterface;
19
use Sylius\Behat\Page\Admin\Product\CreateConfigurableProductPageInterface;
20
use Sylius\Behat\Page\Admin\Product\CreateSimpleProductPageInterface;
21
use Sylius\Behat\Page\Admin\Product\IndexPageInterface;
22
use Sylius\Behat\Page\Admin\Product\IndexPerTaxonPageInterface;
23
use Sylius\Behat\Page\Admin\Product\UpdateConfigurableProductPageInterface;
24
use Sylius\Behat\Page\Admin\Product\UpdateSimpleProductPageInterface;
25
use Sylius\Behat\Page\Admin\ProductReview\IndexPageInterface as ProductReviewIndexPageInterface;
26
use Sylius\Behat\Page\SymfonyPageInterface;
27
use Sylius\Behat\Service\NotificationCheckerInterface;
28
use Sylius\Behat\Service\Resolver\CurrentPageResolverInterface;
29
use Sylius\Component\Core\Model\ChannelInterface;
30
use Sylius\Component\Core\Model\ProductInterface;
31
use Sylius\Behat\Service\SharedStorageInterface;
32
use Sylius\Component\Currency\Model\CurrencyInterface;
33
use Sylius\Component\Product\Model\ProductAssociationTypeInterface;
34
use Sylius\Component\Taxonomy\Model\TaxonInterface;
35
use Webmozart\Assert\Assert;
36
37
/**
38
 * @author Kamil Kokot <[email protected]>
39
 * @author Magdalena Banasiak <[email protected]>
40
 * @author Łukasz Chruściel <[email protected]>
41
 */
42
final class ManagingProductsContext implements Context
43
{
44
    /**
45
     * @var SharedStorageInterface
46
     */
47
    private $sharedStorage;
48
49
    /**x
50
     * @var CreateSimpleProductPageInterface
51
     */
52
    private $createSimpleProductPage;
53
54
    /**
55
     * @var CreateConfigurableProductPageInterface
56
     */
57
    private $createConfigurableProductPage;
58
59
    /**
60
     * @var IndexPageInterface
61
     */
62
    private $indexPage;
63
64
    /**
65
     * @var UpdateSimpleProductPageInterface
66
     */
67
    private $updateSimpleProductPage;
68
69
    /**
70
     * @var UpdateConfigurableProductPageInterface
71
     */
72
    private $updateConfigurableProductPage;
73
74
    /**
75
     * @var ProductReviewIndexPageInterface
76
     */
77
    private $productReviewIndexPage;
78
79
    /**
80
     * @var IndexPerTaxonPageInterface
81
     */
82
    private $indexPerTaxonPage;
83
84
    /**
85
     * @var CurrentPageResolverInterface
86
     */
87
    private $currentPageResolver;
88
89
    /**
90
     * @var NotificationCheckerInterface
91
     */
92
    private $notificationChecker;
93
94
    /**
95
     * @param SharedStorageInterface $sharedStorage
96
     * @param CreateSimpleProductPageInterface $createSimpleProductPage
97
     * @param CreateConfigurableProductPageInterface $createConfigurableProductPage
98
     * @param IndexPageInterface $indexPage
99
     * @param UpdateSimpleProductPageInterface $updateSimpleProductPage
100
     * @param UpdateConfigurableProductPageInterface $updateConfigurableProductPage
101
     * @param ProductReviewIndexPageInterface $productReviewIndexPage
102
     * @param IndexPerTaxonPageInterface $indexPerTaxonPage
103
     * @param CurrentPageResolverInterface $currentPageResolver
104
     * @param NotificationCheckerInterface $notificationChecker
105
     */
106
    public function __construct(
107
        SharedStorageInterface $sharedStorage,
108
        CreateSimpleProductPageInterface $createSimpleProductPage,
109
        CreateConfigurableProductPageInterface $createConfigurableProductPage,
110
        IndexPageInterface $indexPage,
111
        UpdateSimpleProductPageInterface $updateSimpleProductPage,
112
        UpdateConfigurableProductPageInterface $updateConfigurableProductPage,
113
        ProductReviewIndexPageInterface $productReviewIndexPage,
114
        IndexPerTaxonPageInterface $indexPerTaxonPage,
115
        CurrentPageResolverInterface $currentPageResolver,
116
        NotificationCheckerInterface $notificationChecker
117
    ) {
118
        $this->sharedStorage = $sharedStorage;
119
        $this->createSimpleProductPage = $createSimpleProductPage;
120
        $this->createConfigurableProductPage = $createConfigurableProductPage;
121
        $this->indexPage = $indexPage;
122
        $this->updateSimpleProductPage = $updateSimpleProductPage;
123
        $this->updateConfigurableProductPage = $updateConfigurableProductPage;
124
        $this->productReviewIndexPage = $productReviewIndexPage;
125
        $this->indexPerTaxonPage = $indexPerTaxonPage;
126
        $this->currentPageResolver = $currentPageResolver;
127
        $this->notificationChecker = $notificationChecker;
128
    }
129
130
    /**
131
     * @Given I want to create a new simple product
132
     */
133
    public function iWantToCreateANewSimpleProduct()
134
    {
135
        $this->createSimpleProductPage->open();
136
    }
137
138
    /**
139
     * @Given I want to create a new configurable product
140
     */
141
    public function iWantToCreateANewConfigurableProduct()
142
    {
143
        $this->createConfigurableProductPage->open();
144
    }
145
146
    /**
147
     * @When I specify its code as :code
148
     * @When I do not specify its code
149
     */
150
    public function iSpecifyItsCodeAs($code = null)
151
    {
152
        $currentPage = $this->resolveCurrentPage();
153
154
        $currentPage->specifyCode($code);
155
    }
156
157
    /**
158
     * @When I name it :name in :language
159
     * @When I rename it to :name in :language
160
     */
161
    public function iRenameItToIn($name, $language)
162
    {
163
        $currentPage = $this->resolveCurrentPage();
164
165
        $currentPage->nameItIn($name, $language);
166
    }
167
168
    /**
169
     * @When I add it
170
     * @When I try to add it
171
     */
172
    public function iAddIt()
173
    {
174
        /** @var CreatePageInterface $currentPage */
175
        $currentPage = $this->resolveCurrentPage();
176
177
        $currentPage->create();
178
    }
179
180
    /**
181
     * @When I disable its inventory tracking
182
     */
183
    public function iDisableItsTracking()
184
    {
185
        $this->updateSimpleProductPage->disableTracking();
186
    }
187
188
    /**
189
     * @When I enable its inventory tracking
190
     */
191
    public function iEnableItsTracking()
192
    {
193
        $this->updateSimpleProductPage->enableTracking();
194
    }
195
196
    /**
197
     * @When /^I set its(?:| default) price to "(?:€|£|\$)([^"]+)" for "([^"]+)" channel$/
198
     */
199
    public function iSetItsPriceTo($price, $channelName)
200
    {
201
        $this->createSimpleProductPage->specifyPrice($channelName, $price);
202
    }
203
204
    /**
205
     * @When I make it available in channel :channel
206
     */
207
    public function iMakeItAvailableInChannel($channel)
208
    {
209
        $this->createSimpleProductPage->checkChannel($channel);
210
    }
211
212
    /**
213
     * @When I choose :calculatorName calculator
214
     */
215
    public function iChooseCalculator($calculatorName)
216
    {
217
        $this->createSimpleProductPage->choosePricingCalculator($calculatorName);
218
    }
219
220
    /**
221
     * @When I set its slug to :slug
222
     * @When I set its slug to :slug in :language
223
     * @When I remove its slug
224
     */
225
    public function iSetItsSlugToIn($slug = null, $language = 'en_US')
226
    {
227
        $this->createSimpleProductPage->specifySlugIn($slug, $language);
228
    }
229
230
    /**
231
     * @When I enable slug modification
232
     * @When I enable slug modification in :localeCode
233
     */
234
    public function iEnableSlugModification($localeCode = 'en_US')
235
    {
236
        $this->updateSimpleProductPage->activateLanguageTab($localeCode);
237
        $this->updateSimpleProductPage->enableSlugModification($localeCode);
238
    }
239
240
    /**
241
     * @Then the product :productName should appear in the store
242
     * @Then the product :productName should be in the shop
243
     * @Then this product should still be named :productName
244
     */
245
    public function theProductShouldAppearInTheShop($productName)
246
    {
247
        $this->iWantToBrowseProducts();
248
249
        Assert::true(
250
            $this->indexPage->isSingleResourceOnPage(['name' => $productName]),
251
            sprintf('The product with name %s has not been found.', $productName)
252
        );
253
    }
254
255
    /**
256
     * @Given I am browsing products
257
     * @When I want to browse products
258
     */
259
    public function iWantToBrowseProducts()
260
    {
261
        $this->indexPage->open();
262
    }
263
264
    /**
265
     * @When /^I am browsing products from ("([^"]+)" taxon)$/
266
     */
267
    public function iAmBrowsingProductsFromTaxon(TaxonInterface $taxon)
268
    {
269
        $this->indexPerTaxonPage->open(['taxonId' => $taxon->getId()]);
270
    }
271
272
    /**
273
     * @When I filter them by :taxonName taxon
274
     */
275
    public function iFilterThemByTaxon($taxonName)
276
    {
277
        $this->indexPage->filterByTaxon($taxonName);
278
    }
279
280
    /**
281
     * @Then I should( still) see a product with :field :value
282
     */
283
    public function iShouldSeeProductWith($field, $value)
284
    {
285
        Assert::true(
286
            $this->indexPage->isSingleResourceOnPage([$field => $value]),
287
            sprintf('The product with %s "%s" has not been found.', $field, $value)
288
        );
289
    }
290
291
    /**
292
     * @Then I should not see any product with :field :value
293
     */
294
    public function iShouldNotSeeAnyProductWith($field, $value)
295
    {
296
        Assert::false(
297
            $this->indexPage->isSingleResourceOnPage([$field => $value]),
298
            sprintf('The product with %s "%s" has been found.', $field, $value)
299
        );
300
    }
301
302
    /**
303
     * @Then the first product on the list should have :field :value
304
     */
305
    public function theFirstProductOnTheListShouldHave($field, $value)
306
    {
307
        $currentPage = $this->resolveCurrentPage();
308
309
        $actualValue = $currentPage->getColumnFields($field)[0];
310
311
        Assert::same(
312
            $actualValue,
313
            $value,
314
            sprintf('Expected first product\'s %s to be "%s", but it is "%s".', $field, $value, $actualValue)
315
        );
316
    }
317
318
    /**
319
     * @Then the last product on the list should have :field :value
320
     */
321
    public function theLastProductOnTheListShouldHave($field, $value)
322
    {
323
        $columnFields = $this->indexPerTaxonPage->getColumnFields($field);
324
        $actualValue = end($columnFields);
325
326
        Assert::same(
327
            $actualValue,
328
            $value,
329
            sprintf('Expected last product\'s %s to be "%s", but it is "%s".', $field, $value, $actualValue)
330
        );
331
    }
332
333
    /**
334
     * @When I switch the way products are sorted by :field
335
     * @When I start sorting products by :field
336
     * @Given the products are already sorted by :field
337
     */
338
    public function iSortProductsBy($field)
339
    {
340
        $this->indexPage->sortBy($field);
341
    }
342
343
    /**
344
     * @Then I should see :numberOfProducts products in the list
345
     */
346
    public function iShouldSeeProductsInTheList($numberOfProducts)
347
    {
348
        $foundRows = $this->indexPage->countItems();
349
350
        Assert::same(
351
            (int) $numberOfProducts,
352
            $foundRows,
353
            '%s rows with products should appear on page, %s rows has been found'
354
        );
355
    }
356
357
    /**
358
     * @When I delete the :product product
359
     * @When I try to delete the :product product
360
     */
361
    public function iDeleteProduct(ProductInterface $product)
362
    {
363
        $this->sharedStorage->set('product', $product);
364
365
        $this->iWantToBrowseProducts();
366
        $this->indexPage->deleteResourceOnPage(['name' => $product->getName()]);
367
    }
368
369
    /**
370
     * @Then /^(this product) should not exist in the product catalog$/
371
     */
372
    public function productShouldNotExist(ProductInterface $product)
373
    {
374
        $this->iWantToBrowseProducts();
375
376
        Assert::false(
377
            $this->indexPage->isSingleResourceOnPage(['code' => $product->getCode()]),
378
            sprintf('Product with code %s exists but should not.', $product->getCode())
379
        );
380
    }
381
382
    /**
383
     * @Then I should be notified that this product is in use and cannot be deleted
384
     */
385
    public function iShouldBeNotifiedOfFailure()
386
    {
387
        $this->notificationChecker->checkNotification(
388
            "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...
389
            NotificationType::failure()
390
        );
391
    }
392
393
    /**
394
     * @Then /^(this product) should still exist in the product catalog$/
395
     */
396
    public function productShouldExistInTheProductCatalog(ProductInterface $product)
397
    {
398
        $this->theProductShouldAppearInTheShop($product->getName());
399
    }
400
401
    /**
402
     * @When I want to modify the :product product
403
     * @When /^I want to modify (this product)$/
404
     */
405
    public function iWantToModifyAProduct(ProductInterface $product)
406
    {
407
        $this->sharedStorage->set('product', $product);
408
409
        if ($product->isSimple()) {
410
            $this->updateSimpleProductPage->open(['id' => $product->getId()]);
411
            return;
412
        }
413
414
        $this->updateConfigurableProductPage->open(['id' => $product->getId()]);
415
    }
416
417
    /**
418
     * @Then the code field should be disabled
419
     */
420
    public function theCodeFieldShouldBeDisabled()
421
    {
422
        $currentPage = $this->resolveCurrentPage();
423
424
        Assert::true(
425
            $currentPage->isCodeDisabled(),
426
            'Code should be immutable, but it does not.'
427
        );
428
    }
429
430
    /**
431
     * @Then the slug field should not be editable
432
     * @Then the slug field in :localeCode (also )should not be editable
433
     */
434
    public function theSlugFieldShouldNotBeEditable($localeCode = 'en_US')
435
    {
436
        Assert::true(
437
            $this->updateSimpleProductPage->isSlugReadOnlyIn($localeCode),
438
            'Slug should be immutable, but it does not.'
439
        );
440
    }
441
442
    /**
443
     * @Then /^this product price should be "(?:€|£|\$)([^"]+)"$/
444
     */
445
    public function thisProductPriceShouldBeEqualTo($price)
446
    {
447
        $this->assertElementValue('price', $price);
448
    }
449
450
    /**
451
     * @Then this product name should be :name
452
     */
453
    public function thisProductElementShouldBe($name)
454
    {
455
        $this->assertElementValue('name', $name);
456
    }
457
458
    /**
459
     * @Then /^I should be notified that (code|name|slug) is required$/
460
     */
461
    public function iShouldBeNotifiedThatIsRequired($element)
462
    {
463
        $this->assertValidationMessage($element, sprintf('Please enter product %s.', $element));
464
    }
465
466
    /**
467
     * @Then I should be notified that price is required
468
     */
469
    public function iShouldBeNotifiedThatPriceIsRequired()
470
    {
471
        $this->assertValidationMessage('price', 'Please enter the price.');
472
    }
473
474
    /**
475
     * @When I save my changes
476
     * @When I try to save my changes
477
     */
478
    public function iSaveMyChanges()
479
    {
480
        /** @var UpdatePageInterface $currentPage */
481
        $currentPage = $this->resolveCurrentPage();
482
483
        $currentPage->saveChanges();
484
    }
485
486
    /**
487
     * @When /^I change its price to (?:€|£|\$)([^"]+) for "([^"]+)" channel$/
488
     */
489
    public function iChangeItsPriceTo($price, $channelName)
490
    {
491
        $this->updateSimpleProductPage->specifyPrice($channelName, $price);
492
    }
493
494
    /**
495
     * @Given I add the :optionName option to it
496
     */
497
    public function iAddTheOptionToIt($optionName)
498
    {
499
        $this->createConfigurableProductPage->selectOption($optionName);
500
    }
501
502
    /**
503
     * @When I set its :attribute attribute to :value
504
     */
505
    public function iSetItsAttributeTo($attribute, $value)
506
    {
507
        $this->createSimpleProductPage->addAttribute($attribute, $value);
508
    }
509
510
    /**
511
     * @When I remove its :attribute attribute
512
     */
513
    public function iRemoveItsAttribute($attribute)
514
    {
515
        $this->createSimpleProductPage->removeAttribute($attribute);
516
    }
517
518
    /**
519
     * @When I try to add new attributes
520
     */
521
    public function iTryToAddNewAttributes()
522
    {
523
        $this->updateSimpleProductPage->addSelectedAttributes();
524
    }
525
526
    /**
527
     * @Then /^attribute "([^"]+)" of (product "[^"]+") should be "([^"]+)"$/
528
     */
529
    public function itsAttributeShouldBe($attribute, ProductInterface $product, $value)
530
    {
531
        $this->updateSimpleProductPage->open(['id' => $product->getId()]);
532
533
        Assert::same(
534
            $value,
535
            $this->updateSimpleProductPage->getAttributeValue($attribute),
536
            sprintf('ProductAttribute "%s" should have value "%s" but it does not.', $attribute, $value)
537
        );
538
    }
539
540
    /**
541
     * @Then /^(product "[^"]+") should not have a "([^"]+)" attribute$/
542
     */
543
    public function productShouldNotHaveAttribute(ProductInterface $product, $attribute)
544
    {
545
        $this->updateSimpleProductPage->open(['id' => $product->getId()]);
546
547
        Assert::false(
548
            $this->updateSimpleProductPage->hasAttribute($attribute),
549
            sprintf('Product "%s" should not have attribute "%s" but it does.', $product->getName(), $attribute)
550
        );
551
    }
552
553
    /**
554
     * @Then product :product should not have any attributes
555
     * @Then product :product should have :count attribute
556
     */
557
    public function productShouldNotHaveAnyAttributes(ProductInterface $product, $count = 0)
558
    {
559
        $numberOfAttributes = $this->updateSimpleProductPage->getNumberOfAttributes();
560
561
        Assert::same(
562
            (int) $count,
563
            $numberOfAttributes,
564
            sprintf('Product "%s" should have %d attributes, but it has %d.', $product->getName(), $count, $numberOfAttributes)
565
        );
566
    }
567
568
    /**
569
     * @Given product with :element :value should not be added
570
     */
571
    public function productWithNameShouldNotBeAdded($element, $value)
572
    {
573
        $this->iWantToBrowseProducts();
574
575
        Assert::false(
576
            $this->indexPage->isSingleResourceOnPage([$element => $value]),
577
            sprintf('Product with %s %s was created, but it should not.', $element, $value)
578
        );
579
    }
580
581
    /**
582
     * @When I remove its name from :language translation
583
     */
584
    public function iRemoveItsNameFromTranslation($language)
585
    {
586
        $currentPage = $this->resolveCurrentPage();
587
588
        $currentPage->nameItIn('', $language);
589
    }
590
591
    /**
592
     * @Then /^this product should have (?:a|an) "([^"]+)" option$/
593
     */
594
    public function thisProductShouldHaveOption($productOption)
595
    {
596
        $this->updateConfigurableProductPage->isProductOptionChosen($productOption);
597
    }
598
599
    /**
600
     * @Then the option field should be disabled
601
     */
602
    public function theOptionFieldShouldBeDisabled()
603
    {
604
        Assert::true(
605
            $this->updateConfigurableProductPage->isProductOptionsDisabled(),
606
            'Options field should be immutable, but it does not.'
607
        );
608
    }
609
610
    /**
611
     * @When /^I choose main (taxon "([^"]+)")$/
612
     */
613
    public function iChooseMainTaxon(TaxonInterface $taxon)
614
    {
615
        $currentPage = $this->resolveCurrentPage();
616
617
        $currentPage->selectMainTaxon($taxon);
618
    }
619
620
    /**
621
     * @Then /^the slug of the ("[^"]+" product) should(?:| still) be "([^"]+)"$/
622
     * @Then /^the slug of the ("[^"]+" product) should(?:| still) be "([^"]+)" (in the "[^"]+" locale)$/
623
     */
624
    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...
625
    {
626
        $this->updateSimpleProductPage->open(['id' => $product->getId()]);
627
628
        Assert::same(
629
            $this->updateSimpleProductPage->getSlug($locale),
630
            $slug,
631
            'Expected slug %2$s, but found %s.'
632
        );
633
    }
634
635
    /**
636
     * @Then /^(this product) main taxon should be "([^"]+)"$/
637
     */
638
    public function thisProductMainTaxonShouldBe(ProductInterface $product, $taxonName)
639
    {
640
        /** @var UpdatePageInterface $currentPage */
641
        $currentPage = $this->resolveCurrentPage();
642
643
        $currentPage->open(['id' => $product->getId()]);
644
645
        Assert::true(
646
            $this->updateConfigurableProductPage->isMainTaxonChosen($taxonName),
647
            sprintf('The main taxon %s should be chosen, but it does not.', $taxonName)
648
        );
649
    }
650
651
    /**
652
     * @Then /^inventory of (this product) should not be tracked$/
653
     */
654
    public function thisProductShouldNotBeTracked(ProductInterface $product)
655
    {
656
        $this->iWantToModifyAProduct($product);
657
658
        Assert::false(
659
            $this->updateSimpleProductPage->isTracked(),
660
            '"%s" should not be tracked, but it is.'
661
        );
662
    }
663
664
    /**
665
     * @Then /^inventory of (this product) should be tracked$/
666
     */
667
    public function thisProductShouldBeTracked(ProductInterface $product)
668
    {
669
        $this->iWantToModifyAProduct($product);
670
671
        Assert::true(
672
            $this->updateSimpleProductPage->isTracked(),
673
            '"%s" should be tracked, but it is not.'
674
        );
675
    }
676
677
    /**
678
     * @When I attach the :path image with a code :code
679
     */
680
    public function iAttachImageWithACode($path, $code)
681
    {
682
        /** @var CreatePageInterface|UpdatePageInterface $currentPage */
683
        $currentPage = $this->resolveCurrentPage();
684
685
        $currentPage->attachImage($path, $code);
686
    }
687
688
    /**
689
     * @When I attach the :path image without a code
690
     */
691
    public function iAttachImageWithoutACode($path)
692
    {
693
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
694
        $currentPage = $this->resolveCurrentPage();
695
696
        $currentPage->attachImage($path);
697
    }
698
699
    /**
700
     * @When I associate as :productAssociationType the :productName product
701
     * @When I associate as :productAssociationType the :firstProductName and :secondProductName products
702
     */
703
    public function iAssociateProductsAsProductAssociation(
704
        ProductAssociationTypeInterface $productAssociationType,
705
        ...$productsNames
706
    ) {
707
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
708
        $currentPage = $this->resolveCurrentPage();
709
710
        $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...
711
    }
712
713
    /**
714
     * @When I remove an associated product :productName from :productAssociationType
715
     */
716
    public function iRemoveAnAssociatedProductFromProductAssociation(
717
        $productName,
718
        ProductAssociationTypeInterface $productAssociationType
719
    ) {
720
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
721
        $currentPage = $this->resolveCurrentPage();
722
723
        $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...
724
    }
725
726
    /**
727
     * @Then /^(this product) should have(?:| also) an image with a code "([^"]*)"$/
728
     * @Then /^the (product "[^"]+") should have(?:| also) an image with a code "([^"]*)"$/
729
     */
730
    public function thisProductShouldHaveAnImageWithCode(ProductInterface $product, $code)
731
    {
732
        $this->sharedStorage->set('product', $product);
733
734
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
735
        $currentPage = $this->resolveCurrentPage();
736
737
        Assert::true(
738
            $currentPage->isImageWithCodeDisplayed($code),
739
            sprintf('Image with a code %s should have been displayed.', $code)
740
        );
741
    }
742
743
    /**
744
     * @Then /^(this product) should not have(?:| also) an image with a code "([^"]*)"$/
745
     */
746
    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...
747
    {
748
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
749
        $currentPage = $this->resolveCurrentPage();
750
751
        Assert::false(
752
            $currentPage->isImageWithCodeDisplayed($code),
753
            sprintf('Image with a code %s should not have been displayed.', $code)
754
        );
755
    }
756
757
    /**
758
     * @When I change the image with the :code code to :path
759
     */
760
    public function iChangeItsImageToPathForTheCode($path, $code)
761
    {
762
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
763
        $currentPage = $this->resolveCurrentPage();
764
765
        $currentPage->changeImageWithCode($code, $path);
766
    }
767
768
    /**
769
     * @When /^I remove(?:| also) an image with a code "([^"]*)"$/
770
     */
771
    public function iRemoveAnImageWithACode($code)
772
    {
773
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
774
        $currentPage = $this->resolveCurrentPage();
775
776
        $currentPage->removeImageWithCode($code);
777
    }
778
779
    /**
780
     * @When I remove the first image
781
     */
782
    public function iRemoveTheFirstImage()
783
    {
784
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
785
        $currentPage = $this->resolveCurrentPage();
786
787
        $currentPage->removeFirstImage();
788
    }
789
790
    /**
791
     * @Then /^(this product) should not have any images$/
792
     */
793
    public function thisProductShouldNotHaveImages(ProductInterface $product)
794
    {
795
        $this->iWantToModifyAProduct($product);
796
797
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
798
        $currentPage = $this->resolveCurrentPage();
799
800
        Assert::same(
801
            0,
802
            $currentPage->countImages(),
803
            'This product has %2$s, but it should not have.'
804
        );
805
    }
806
807
    /**
808
     * @Then the image code field should be disabled
809
     */
810
    public function theImageCodeFieldShouldBeDisabled()
811
    {
812
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
813
        $currentPage = $this->resolveCurrentPage();
814
815
        Assert::true(
816
            $currentPage->isImageCodeDisabled(),
817
            'Image code field should be disabled but it is not.'
818
        );
819
    }
820
821
    /**
822
     * @Then I should be notified that the image with this code already exists
823
     */
824
    public function iShouldBeNotifiedThatTheImageWithThisCodeAlreadyExists()
825
    {
826
        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...
827
    }
828
829
    /**
830
     * @Then I should be notified that an image code is required
831
     */
832
    public function iShouldBeNotifiedThatAnImageCodeIsRequired()
833
    {
834
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
835
        $currentPage = $this->resolveCurrentPage();
836
837
        Assert::same(
838
            $currentPage->getValidationMessageForImage(),
839
            'Please enter an image code.'
840
        );
841
    }
842
843
    /**
844
     * @Then there should still be only one image in the :product product
845
     */
846
    public function thereShouldStillBeOnlyOneImageInThisTaxon(ProductInterface $product)
847
    {
848
        $this->iWantToModifyAProduct($product);
849
850
        /** @var UpdateSimpleProductPageInterface|UpdateConfigurableProductPageInterface $currentPage */
851
        $currentPage = $this->resolveCurrentPage();
852
853
        Assert::same(
854
            1,
855
            $currentPage->countImages(),
856
            'This product has %2$s images, but it should have only one.'
857
        );
858
    }
859
860
    /**
861
     * @Then /^there should be no reviews of (this product)$/
862
     */
863
    public function thereAreNoProductReviews(ProductInterface $product)
864
    {
865
        $this->productReviewIndexPage->open();
866
867
        Assert::false(
868
            $this->productReviewIndexPage->isSingleResourceOnPage(['reviewSubject' => $product->getName()]),
869
            sprintf('There should be no reviews of %s.', $product->getName())
870
        );
871
    }
872
873
    /**
874
     * @Then /^the product for ("[^"]+" currency) and ("[^"]+" channel) should be priced at "(?:€|£|\$)([^"]+)"$/
875
     */
876
    public function theProductForCurrencyAndChannelShouldBePricedAt(CurrencyInterface $currency, ChannelInterface $channel, $price)
877
    {
878
        Assert::same(
879
            $this->updateSimpleProductPage->getPricingConfigurationForChannelAndCurrencyCalculator($channel, $currency),
880
            $price
881
        );
882
    }
883
884
    /**
885
     * @Then this product should( also) have an association :productAssociationType with product :productName
886
     * @Then this product should( also) have an association :productAssociationType with products :firstProductName and :secondProductName
887
     */
888
    public function theProductShouldHaveAnAssociationWithProducts(
889
        ProductAssociationTypeInterface $productAssociationType,
890
        ...$productsNames
891
    ) {
892
        foreach ($productsNames as $productName) {
893
            Assert::true(
894
                $this->updateSimpleProductPage->hasAssociatedProduct($productName, $productAssociationType),
895
                sprintf(
896
                    'This product should have an association %s with product %s, but it does not.',
897
                    $productAssociationType->getName(),
898
                    $productName
899
                )
900
            );
901
        }
902
    }
903
904
    /**
905
     * @Then this product should not have an association :productAssociationType with product :productName
906
     */
907
    public function theProductShouldNotHaveAnAssociationWithProducts(
908
        ProductAssociationTypeInterface $productAssociationType,
909
        $productName
910
    ) {
911
        Assert::false(
912
            $this->updateSimpleProductPage->hasAssociatedProduct($productName, $productAssociationType),
913
            sprintf(
914
                'This product should not have an association %s with product %s, but it does.',
915
                $productAssociationType->getName(),
916
                $productName
917
            )
918
        );
919
    }
920
921
    /**
922
     * @Then I should be notified that simple product code has to be unique
923
     */
924
    public function iShouldBeNotifiedThatSimpleProductCodeHasToBeUnique()
925
    {
926
        $this->assertValidationMessage('code', 'Simple product code must be unique among all products and product variants.');
927
    }
928
929
    /**
930
     * @Then I should be notified that code has to be unique
931
     */
932
    public function iShouldBeNotifiedThatCodeHasToBeUnique()
933
    {
934
        $this->assertValidationMessage('code', 'Product code must be unique.');
935
    }
936
937
    /**
938
     * @Then I should be notified that price must be defined for every channel
939
     */
940
    public function iShouldBeNotifiedThatPriceMustBeDefinedForEveryChannel()
941
    {
942
        $this->assertValidationMessage('channel_pricings', 'You must define price for every channel.');
943
    }
944
945
    /**
946
     * @Then they should have order like :firstProductName, :secondProductName and :thirdProductName
947
     */
948
    public function theyShouldHaveOrderLikeAnd(...$productNames)
949
    {
950
        Assert::true(
951
            $this->indexPerTaxonPage->hasProductsInOrder($productNames),
952
            'The products have wrong order.'
953
        );
954
    }
955
956
    /**
957
     * @When I save my new configuration
958
     */
959
    public function iSaveMyNewConfiguration()
960
    {
961
        $this->indexPerTaxonPage->savePositions();
962
    }
963
964
    /**
965
     * @When I set the position of :productName to :position
966
     */
967
    public function iSetThePositionOfTo($productName, $position)
968
    {
969
        $this->indexPerTaxonPage->setPositionOfProduct($productName, (int) $position);
970
    }
971
972
    /**
973
     * @Then this product should( still) have slug :value in :language
974
     */
975
    public function thisProductElementShouldHaveSlugIn($slug, $language)
976
    {
977
        Assert::same(
978
            $this->updateSimpleProductPage->getSlug($language),
979
            $slug,
980
            'Expected slug %2$s, but found %s.'
981
        );
982
    }
983
984
    /**
985
     * @When I set its shipping category as :shippingCategoryName
986
     */
987
    public function iSetItsShippingCategoryAs($shippingCategoryName)
988
    {
989
        $this->createSimpleProductPage->selectShippingCategory($shippingCategoryName);
990
    }
991
992
    /**
993
     * @Then /^(it|this product) should be priced at (?:€|£|\$)([^"]+) for channel "([^"]+)"$/
994
     * @Then /^(product "[^"]+") should be priced at (?:€|£|\$)([^"]+) for channel "([^"]+)"$/
995
     */
996
    public function itShouldBePricedAtForChannel(ProductInterface $product, $price, $channelName)
997
    {
998
        $this->updateSimpleProductPage->open(['id' => $product->getId()]);
999
1000
        Assert::same(
1001
            $this->updateSimpleProductPage->getPriceForChannel($channelName),
1002
            $price
1003
        );
1004
    }
1005
1006
    /**
1007
     * @Then /^(this product) should no longer have price for channel "([^"]+)"$/
1008
     */
1009
    public function thisProductShouldNoLongerHavePriceForChannel(ProductInterface $product, $channelName)
1010
    {
1011
        $this->updateSimpleProductPage->open(['id' => $product->getId()]);
1012
1013
        try {
1014
            $this->updateSimpleProductPage->getPriceForChannel($channelName);
1015
        } catch (ElementNotFoundException $exception) {
1016
            return;
1017
        }
1018
1019
        throw new \Exception(
1020
            sprintf('Product "%s" should not have price defined for channel "%s".', $product->getName(), $channelName)
1021
        );
1022
    }
1023
1024
    /**
1025
     * @param string $element
1026
     * @param string $value
1027
     */
1028
    private function assertElementValue($element, $value)
1029
    {
1030
        /** @var UpdatePageInterface $currentPage */
1031
        $currentPage = $this->resolveCurrentPage();
1032
1033
        Assert::isInstanceOf($currentPage, UpdatePageInterface::class);
1034
1035
        Assert::true(
1036
            $currentPage->hasResourceValues(
1037
                [$element => $value]
1038
            ),
1039
            sprintf('Product should have %s with %s value.', $element, $value)
1040
        );
1041
    }
1042
1043
    /**
1044
     * @param string $element
1045
     * @param string $message
1046
     */
1047
    private function assertValidationMessage($element, $message)
1048
    {
1049
        /** @var CreatePageInterface|UpdatePageInterface $currentPage */
1050
        $currentPage = $this->resolveCurrentPage();
1051
1052
        Assert::same($currentPage->getValidationMessage($element), $message);
1053
    }
1054
1055
    /**
1056
     * @return SymfonyPageInterface
1057
     */
1058
    private function resolveCurrentPage()
1059
    {
1060
        return $this->currentPageResolver->getCurrentPageWithForm([
1061
            $this->indexPage,
1062
            $this->indexPerTaxonPage,
1063
            $this->createSimpleProductPage,
1064
            $this->createConfigurableProductPage,
1065
            $this->updateSimpleProductPage,
1066
            $this->updateConfigurableProductPage,
1067
        ]);
1068
    }
1069
}
1070