Completed
Push — master ( d81c19...f57266 )
by Kamil
20s
created

Page/Admin/Product/UpdateSimpleProductPage.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Page\Admin\Product;
15
16
use Behat\Mink\Driver\Selenium2Driver;
17
use Behat\Mink\Element\NodeElement;
18
use Sylius\Behat\Behaviour\ChecksCodeImmutability;
19
use Sylius\Behat\Page\Admin\Crud\UpdatePage as BaseUpdatePage;
20
use Sylius\Behat\Service\AutocompleteHelper;
21
use Sylius\Behat\Service\SlugGenerationHelper;
22
use Sylius\Component\Core\Model\ChannelInterface;
23
use Sylius\Component\Core\Model\TaxonInterface;
24
use Sylius\Component\Currency\Model\CurrencyInterface;
25
use Sylius\Component\Product\Model\ProductAssociationTypeInterface;
26
use Webmozart\Assert\Assert;
27
28
class UpdateSimpleProductPage extends BaseUpdatePage implements UpdateSimpleProductPageInterface
29
{
30
    use ChecksCodeImmutability;
31
32
    /** @var array */
33
    private $imageUrls = [];
34
35
    /**
36
     * {@inheritdoc}
37
     */
38
    public function nameItIn($name, $localeCode)
39
    {
40
        $this->activateLanguageTab($localeCode);
41
        $this->getElement('name', ['%locale%' => $localeCode])->setValue($name);
42
43
        if ($this->getDriver() instanceof Selenium2Driver) {
44
            SlugGenerationHelper::waitForSlugGeneration(
45
                $this->getSession(),
46
                $this->getElement('slug', ['%locale%' => $localeCode])
47
            );
48
        }
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function specifyPrice($channelName, $price)
55
    {
56
        $this->getElement('price', ['%channelName%' => $channelName])->setValue($price);
57
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62
    public function specifyOriginalPrice($channelName, $originalPrice)
63
    {
64
        $this->getElement('original_price', ['%channelName%' => $channelName])->setValue($originalPrice);
65
    }
66
67
    public function addSelectedAttributes()
68
    {
69
        $this->clickTabIfItsNotActive('attributes');
70
        $this->getDocument()->pressButton('Add attributes');
71
72
        $form = $this->getDocument()->find('css', 'form');
73
74
        $this->getDocument()->waitFor(1, function () use ($form) {
75
            return $form->hasClass('loading');
76
        });
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82
    public function removeAttribute($attributeName, $localeCode)
83
    {
84
        $this->clickTabIfItsNotActive('attributes');
85
86
        $this->getElement('attribute_delete_button', ['%attributeName%' => $attributeName, '$localeCode%' => $localeCode])->press();
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    public function getAttributeValue($attribute, $localeCode)
93
    {
94
        $this->clickTabIfItsNotActive('attributes');
95
        $this->clickLocaleTabIfItsNotActive($localeCode);
96
97
        return $this->getElement('attribute', ['%attributeName%' => $attribute, '%localeCode%' => $localeCode])->getValue();
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103
    public function getAttributeValidationErrors($attributeName, $localeCode)
104
    {
105
        $this->clickTabIfItsNotActive('attributes');
106
        $this->clickLocaleTabIfItsNotActive($localeCode);
107
108
        $validationError = $this->getElement('attribute_element')->find('css', '.sylius-validation-error');
109
110
        return $validationError->getText();
111
    }
112
113
    /**
114
     * {@inheritdoc}
115
     */
116
    public function getNumberOfAttributes()
117
    {
118
        return count($this->getDocument()->findAll('css', '.attribute'));
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function hasAttribute($attributeName)
125
    {
126
        return null !== $this->getDocument()->find('css', sprintf('.attribute .label:contains("%s")', $attributeName));
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132
    public function selectMainTaxon(TaxonInterface $taxon)
133
    {
134
        $this->openTaxonBookmarks();
135
136
        $mainTaxonElement = $this->getElement('main_taxon')->getParent();
137
138
        AutocompleteHelper::chooseValue($this->getSession(), $mainTaxonElement, $taxon->getName());
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144
    public function isMainTaxonChosen($taxonName)
145
    {
146
        $this->openTaxonBookmarks();
147
148
        return $taxonName === $this->getDocument()->find('css', '.search > .text')->getText();
149
    }
150
151
    public function disableTracking()
152
    {
153
        $this->getElement('tracked')->uncheck();
154
    }
155
156
    public function enableTracking()
157
    {
158
        $this->getElement('tracked')->check();
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function isTracked()
165
    {
166
        return $this->getElement('tracked')->isChecked();
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function enableSlugModification($locale)
173
    {
174
        SlugGenerationHelper::enableSlugModification(
175
            $this->getSession(),
176
            $this->getElement('toggle_slug_modification_button', ['%locale%' => $locale])
177
        );
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183
    public function isImageWithTypeDisplayed($type)
184
    {
185
        $imageElement = $this->getImageElementByType($type);
186
187
        $imageUrl = $imageElement ? $imageElement->find('css', 'img')->getAttribute('src') : $this->provideImageUrlForType($type);
188
        if (null === $imageElement && null === $imageUrl) {
189
            return false;
190
        }
191
192
        $this->getDriver()->visit($imageUrl);
193
        $pageText = $this->getDocument()->getText();
194
        $this->getDriver()->back();
195
196
        return false === stripos($pageText, '404 Not Found');
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202
    public function attachImage($path, $type = null)
203
    {
204
        $this->clickTabIfItsNotActive('media');
205
206
        $filesPath = $this->getParameter('files_path');
207
208
        $this->getDocument()->clickLink('Add');
209
210
        $imageForm = $this->getLastImageElement();
211
        if (null !== $type) {
212
            $imageForm->fillField('Type', $type);
213
        }
214
215
        $imageForm->find('css', 'input[type="file"]')->attachFile($filesPath . $path);
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221
    public function changeImageWithType($type, $path)
222
    {
223
        $filesPath = $this->getParameter('files_path');
224
225
        $imageForm = $this->getImageElementByType($type);
226
        $imageForm->find('css', 'input[type="file"]')->attachFile($filesPath . $path);
227
    }
228
229
    /**
230
     * {@inheritdoc}
231
     */
232
    public function removeImageWithType($type)
233
    {
234
        $this->clickTabIfItsNotActive('media');
235
236
        $imageElement = $this->getImageElementByType($type);
237
        $imageSourceElement = $imageElement->find('css', 'img');
238
        if (null !== $imageSourceElement) {
239
            $this->saveImageUrlForType($type, $imageSourceElement->getAttribute('src'));
240
        }
241
242
        $imageElement->clickLink('Delete');
243
    }
244
245
    public function removeFirstImage()
246
    {
247
        $this->clickTabIfItsNotActive('media');
248
        $imageElement = $this->getFirstImageElement();
249
        $imageTypeElement = $imageElement->find('css', 'input[type=text]');
250
        $imageSourceElement = $imageElement->find('css', 'img');
251
252
        if (null !== $imageTypeElement && null !== $imageSourceElement) {
253
            $this->saveImageUrlForType(
254
                $imageTypeElement->getValue(),
255
                $imageSourceElement->getAttribute('src')
256
            );
257
        }
258
        $imageElement->clickLink('Delete');
259
    }
260
261
    /**
262
     * {@inheritdoc}
263
     */
264
    public function modifyFirstImageType($type)
265
    {
266
        $this->clickTabIfItsNotActive('media');
267
268
        $firstImage = $this->getFirstImageElement();
269
        $this->setImageType($firstImage, $type);
270
    }
271
272
    /**
273
     * {@inheritdoc}
274
     */
275
    public function countImages()
276
    {
277
        $imageElements = $this->getImageElements();
278
279
        return count($imageElements);
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     */
285
    public function isSlugReadonlyIn($locale)
286
    {
287
        return SlugGenerationHelper::isSlugReadonly(
288
            $this->getSession(),
289
            $this->getElement('slug', ['%locale%' => $locale])
290
        );
291
    }
292
293
    /**
294
     * {@inheritdoc}
295
     */
296
    public function associateProducts(ProductAssociationTypeInterface $productAssociationType, array $productsNames)
297
    {
298
        $this->clickTab('associations');
299
300
        Assert::isInstanceOf($this->getDriver(), Selenium2Driver::class);
301
302
        $dropdown = $this->getElement('association_dropdown', [
303
            '%association%' => $productAssociationType->getName(),
304
        ]);
305
        $dropdown->click();
306
307
        foreach ($productsNames as $productName) {
308
            $dropdown->waitFor(5, function () use ($productName, $productAssociationType) {
309
                return $this->hasElement('association_dropdown_item', [
310
                    '%association%' => $productAssociationType->getName(),
311
                    '%item%' => $productName,
312
                ]);
313
            });
314
315
            $item = $this->getElement('association_dropdown_item', [
316
                '%association%' => $productAssociationType->getName(),
317
                '%item%' => $productName,
318
            ]);
319
            $item->click();
320
        }
321
    }
322
323
    /**
324
     * {@inheritdoc}
325
     */
326
    public function hasAssociatedProduct($productName, ProductAssociationTypeInterface $productAssociationType)
327
    {
328
        $this->clickTabIfItsNotActive('associations');
329
330
        return $this->hasElement('association_dropdown_item', [
331
            '%association%' => $productAssociationType->getName(),
332
            '%item%' => $productName,
333
        ]);
334
    }
335
336
    /**
337
     * {@inheritdoc}
338
     */
339
    public function removeAssociatedProduct($productName, ProductAssociationTypeInterface $productAssociationType)
340
    {
341
        $this->clickTabIfItsNotActive('associations');
342
343
        $item = $this->getElement('association_dropdown_item_selected', [
344
            '%association%' => $productAssociationType->getName(),
345
            '%item%' => $productName,
346
        ]);
347
348
        $deleteIcon = $item->find('css', 'i.delete');
349
        Assert::notNull($deleteIcon);
350
        $deleteIcon->click();
351
    }
352
353
    /**
354
     * {@inheritdoc}
355
     */
356
    public function getPricingConfigurationForChannelAndCurrencyCalculator(ChannelInterface $channel, CurrencyInterface $currency)
357
    {
358
        $priceConfigurationElement = $this->getElement('pricing_configuration');
359
        $priceElement = $priceConfigurationElement
360
            ->find('css', sprintf('label:contains("%s %s")', $channel->getCode(), $currency->getCode()))->getParent();
361
362
        return $priceElement->find('css', 'input')->getValue();
363
    }
364
365
    /**
366
     * {@inheritdoc}
367
     */
368
    public function getSlug($locale)
369
    {
370
        $this->activateLanguageTab($locale);
371
372
        return $this->getElement('slug', ['%locale%' => $locale])->getValue();
373
    }
374
375
    /**
376
     * {@inheritdoc}
377
     */
378
    public function specifySlugIn($slug, $locale)
379
    {
380
        $this->activateLanguageTab($locale);
381
382
        $this->getElement('slug', ['%locale%' => $locale])->setValue($slug);
383
    }
384
385
    /**
386
     * {@inheritdoc}
387
     */
388
    public function activateLanguageTab($locale)
389
    {
390
        if (!$this->getDriver() instanceof Selenium2Driver) {
391
            return;
392
        }
393
394
        $languageTabTitle = $this->getElement('language_tab', ['%locale%' => $locale]);
395
        if (!$languageTabTitle->hasClass('active')) {
396
            $languageTabTitle->click();
397
        }
398
    }
399
400
    public function getPriceForChannel($channelName)
401
    {
402
        return $this->getElement('price', ['%channelName%' => $channelName])->getValue();
403
    }
404
405
    /**
406
     * {@inheritdoc}
407
     */
408
    public function getOriginalPriceForChannel($channelName)
409
    {
410
        return $this->getElement('original_price', ['%channelName%' => $channelName])->getValue();
411
    }
412
413
    /**
414
     * {@inheritdoc}
415
     */
416
    public function isShippingRequired()
417
    {
418
        return $this->getElement('shipping_required')->isChecked();
419
    }
420
421
    /**
422
     * {@inheritdoc}
423
     */
424
    protected function getCodeElement()
425
    {
426
        return $this->getElement('code');
427
    }
428
429
    /**
430
     * {@inheritdoc}
431
     */
432
    protected function getElement($name, array $parameters = [])
433
    {
434
        if (!isset($parameters['%locale%'])) {
435
            $parameters['%locale%'] = 'en_US';
436
        }
437
438
        return parent::getElement($name, $parameters);
439
    }
440
441
    /**
442
     * {@inheritdoc}
443
     */
444
    protected function getDefinedElements()
445
    {
446
        return array_merge(parent::getDefinedElements(), [
447
            'association_dropdown' => '.field > label:contains("%association%") ~ .product-select',
448
            'association_dropdown_item' => '.field > label:contains("%association%") ~ .product-select > div.menu > div.item:contains("%item%")',
449
            'association_dropdown_item_selected' => '.field > label:contains("%association%") ~ .product-select > a.label:contains("%item%")',
450
            'attribute' => '.tab[data-tab="%localeCode%"] .attribute:contains("%attributeName%") input',
451
            'attribute_element' => '.attribute',
452
            'attribute_delete_button' => '.tab[data-tab="%localeCode%"] .attribute .label:contains("%attributeName%") ~ button',
453
            'code' => '#sylius_product_code',
454
            'images' => '#sylius_product_images',
455
            'language_tab' => '[data-locale="%locale%"] .title',
456
            'locale_tab' => '#attributesContainer .menu [data-tab="%localeCode%"]',
457
            'name' => '#sylius_product_translations_%locale%_name',
458
            'original_price' => '#sylius_product_variant_channelPricings > .field:contains("%channelName%") input[name$="[originalPrice]"]',
459
            'price' => '#sylius_product_variant_channelPricings > .field:contains("%channelName%") input[name$="[price]"]',
460
            'pricing_configuration' => '#sylius_calculator_container',
461
            'main_taxon' => '#sylius_product_mainTaxon',
462
            'shipping_required' => '#sylius_product_variant_shippingRequired',
463
            'slug' => '#sylius_product_translations_%locale%_slug',
464
            'tab' => '.menu [data-tab="%name%"]',
465
            'taxonomy' => 'a[data-tab="taxonomy"]',
466
            'tracked' => '#sylius_product_variant_tracked',
467
            'toggle_slug_modification_button' => '[data-locale="%locale%"] .toggle-product-slug-modification',
468
        ]);
469
    }
470
471
    private function openTaxonBookmarks()
472
    {
473
        $this->getElement('taxonomy')->click();
474
    }
475
476
    /**
477
     * @param string $tabName
478
     */
479
    private function clickTabIfItsNotActive($tabName)
480
    {
481
        $attributesTab = $this->getElement('tab', ['%name%' => $tabName]);
482
        if (!$attributesTab->hasClass('active')) {
483
            $attributesTab->click();
484
        }
485
    }
486
487
    /**
488
     * @param string $tabName
489
     */
490
    private function clickTab($tabName)
491
    {
492
        $attributesTab = $this->getElement('tab', ['%name%' => $tabName]);
493
        $attributesTab->click();
494
    }
495
496
    /**
497
     * @param string $localeCode
498
     */
499
    private function clickLocaleTabIfItsNotActive($localeCode)
500
    {
501
        $localeTab = $this->getElement('locale_tab', ['%localeCode%' => $localeCode]);
502
        if (!$localeTab->hasClass('active')) {
503
            $localeTab->click();
504
        }
505
    }
506
507
    /**
508
     * @param string $type
509
     *
510
     * @return NodeElement
511
     */
512
    private function getImageElementByType($type)
513
    {
514
        $images = $this->getElement('images');
515
        $typeInput = $images->find('css', 'input[value="' . $type . '"]');
516
517
        if (null === $typeInput) {
518
            return null;
519
        }
520
521
        return $typeInput->getParent()->getParent()->getParent();
522
    }
523
524
    /**
525
     * @return NodeElement[]
526
     */
527
    private function getImageElements()
528
    {
529
        $images = $this->getElement('images');
530
531
        return $images->findAll('css', 'div[data-form-collection="item"]');
532
    }
533
534
    /**
535
     * @return NodeElement
536
     */
537
    private function getLastImageElement()
538
    {
539
        $imageElements = $this->getImageElements();
540
541
        Assert::notEmpty($imageElements);
542
543
        return end($imageElements);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression end($imageElements); of type Behat\Mink\Element\NodeElement|false adds false to the return on line 543 which is incompatible with the return type documented by Sylius\Behat\Page\Admin\...ge::getLastImageElement of type Behat\Mink\Element\NodeElement. It seems like you forgot to handle an error condition.
Loading history...
544
    }
545
546
    /**
547
     * @return NodeElement
548
     */
549
    private function getFirstImageElement()
550
    {
551
        $imageElements = $this->getImageElements();
552
553
        Assert::notEmpty($imageElements);
554
555
        return reset($imageElements);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression reset($imageElements); of type Behat\Mink\Element\NodeElement|false adds false to the return on line 555 which is incompatible with the return type documented by Sylius\Behat\Page\Admin\...e::getFirstImageElement of type Behat\Mink\Element\NodeElement. It seems like you forgot to handle an error condition.
Loading history...
556
    }
557
558
    /**
559
     * @param NodeElement $imageElement
560
     * @param string $type
561
     */
562
    private function setImageType(NodeElement $imageElement, $type)
563
    {
564
        $typeField = $imageElement->findField('Type');
565
        $typeField->setValue($type);
566
    }
567
568
    private function provideImageUrlForType(string $type): ?string
569
    {
570
        return $this->imageUrls[$type] ?? null;
571
    }
572
573
    private function saveImageUrlForType(string $type, string $imageUrl): void
574
    {
575
        if (false !== strpos($imageUrl, 'data:image/jpeg')) {
576
            return;
577
        }
578
579
        $this->imageUrls[$type] = $imageUrl;
580
    }
581
}
582