Completed
Push — missing-product-variant-variab... ( 2e9a80...01bfdb )
by Kamil
22:39
created

UpdateSimpleProductPage::selectMainTaxon()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

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 5
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sylius\Behat\Page\Admin\Product;
13
14
use Behat\Mink\Driver\Selenium2Driver;
15
use Behat\Mink\Element\NodeElement;
16
use Sylius\Behat\Behaviour\ChecksCodeImmutability;
17
use Sylius\Behat\Page\Admin\Crud\UpdatePage as BaseUpdatePage;
18
use Sylius\Behat\Service\AutocompleteHelper;
19
use Sylius\Component\Core\Model\ChannelInterface;
20
use Sylius\Component\Core\Model\TaxonInterface;
21
use Sylius\Component\Currency\Model\CurrencyInterface;
22
use Sylius\Component\Product\Model\ProductAssociationTypeInterface;
23
use Webmozart\Assert\Assert;
24
25
/**
26
 * @author Łukasz Chruściel <[email protected]>
27
 * @author Gorka Laucirica <[email protected]>
28
 */
29
class UpdateSimpleProductPage extends BaseUpdatePage implements UpdateSimpleProductPageInterface
30
{
31
    use ChecksCodeImmutability;
32
33
    /**
34
     * {@inheritdoc}
35
     */
36
    public function nameItIn($name, $localeCode)
37
    {
38
        $this->activateLanguageTab($localeCode);
39
        $this->getElement('name', ['%locale%' => $localeCode])->setValue($name);
40
41
        $this->waitForSlugGenerationIfNecessary($localeCode);
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47
    public function specifyPrice($channelName, $price)
48
    {
49
        $this->getElement('price', ['%channelName%' => $channelName])->setValue($price);
50
    }
51
52
    /**
53
     * {@inheritdoc}
54
     */
55
    public function specifyOriginalPrice($channelName, $originalPrice)
56
    {
57
        $this->getElement('original_price', ['%channelName%' => $channelName])->setValue($originalPrice);
58
    }
59
60
    public function addSelectedAttributes()
61
    {
62
        $this->clickTabIfItsNotActive('attributes');
63
        $this->getDocument()->pressButton('Add attributes');
64
65
        $form = $this->getDocument()->find('css', 'form');
66
67
        $this->getDocument()->waitFor(1, function () use ($form) {
68
            return $form->hasClass('loading');
69
        });
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function removeAttribute($attributeName, $localeCode)
76
    {
77
        $this->clickTabIfItsNotActive('attributes');
78
79
        $this->getElement('attribute_delete_button', ['%attributeName%' => $attributeName, '$localeCode%' => $localeCode])->press();
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function getAttributeValue($attribute, $localeCode)
86
    {
87
        $this->clickTabIfItsNotActive('attributes');
88
        $this->clickLocaleTabIfItsNotActive($localeCode);
89
90
        return $this->getElement('attribute', ['%attributeName%' => $attribute, '%localeCode%' => $localeCode])->getValue();
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96
    public function getNumberOfAttributes()
97
    {
98
        return count($this->getDocument()->findAll('css', '.attribute'));
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104
    public function hasAttribute($attributeName)
105
    {
106
        return null !== $this->getDocument()->find('css', sprintf('.attribute .label:contains("%s")', $attributeName));
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112
    public function selectMainTaxon(TaxonInterface $taxon)
113
    {
114
        $this->openTaxonBookmarks();
115
116
        Assert::isInstanceOf($this->getDriver(), Selenium2Driver::class);
117
118
        $mainTaxonElement = $this->getElement('main_taxon')->getParent();
119
120
        AutocompleteHelper::chooseValue($this->getSession(), $mainTaxonElement, $taxon->getName());
0 ignored issues
show
Bug introduced by
It seems like $mainTaxonElement defined by $this->getElement('main_taxon')->getParent() on line 118 can be null; however, Sylius\Behat\Service\Aut...teHelper::chooseValue() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function isMainTaxonChosen($taxonName)
127
    {
128
        $this->openTaxonBookmarks();
129
130
        return $taxonName === $this->getDocument()->find('css', '.search > .text')->getText();
131
    }
132
133
    public function disableTracking()
134
    {
135
        $this->getElement('tracked')->uncheck();
136
    }
137
138
    public function enableTracking()
139
    {
140
        $this->getElement('tracked')->check();
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146
    public function isTracked()
147
    {
148
        return $this->getElement('tracked')->isChecked();
149
    }
150
151
    /**
152
     * {@inheritdoc}
153
     */
154
    public function enableSlugModification($locale)
155
    {
156
        $this->getElement('toggle_slug_modification_button', ['%locale%' => $locale])->press();
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162
    public function isImageWithTypeDisplayed($type)
163
    {
164
        $imageElement = $this->getImageElementByType($type);
165
166
        if (null === $imageElement) {
167
            return false;
168
        }
169
170
        $imageUrl = $imageElement->find('css', 'img')->getAttribute('src');
171
        $this->getDriver()->visit($imageUrl);
172
        $pageText = $this->getDocument()->getText();
173
        $this->getDriver()->back();
174
175
        return false === stripos($pageText, '404 Not Found');
176
    }
177
178
    /**
179
     * {@inheritdoc}
180
     */
181
    public function attachImage($path, $type = null)
182
    {
183
        $this->clickTabIfItsNotActive('media');
184
185
        $filesPath = $this->getParameter('files_path');
186
187
        $this->getDocument()->clickLink('Add');
188
189
        $imageForm = $this->getLastImageElement();
190
        if (null !== $type) {
191
            $imageForm->fillField('Type', $type);
192
        }
193
194
        $imageForm->find('css', 'input[type="file"]')->attachFile($filesPath.$path);
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200
    public function changeImageWithType($type, $path)
201
    {
202
        $filesPath = $this->getParameter('files_path');
203
204
        $imageForm = $this->getImageElementByType($type);
205
        $imageForm->find('css', 'input[type="file"]')->attachFile($filesPath.$path);
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211
    public function removeImageWithType($type)
212
    {
213
        $this->clickTabIfItsNotActive('media');
214
215
        $imageElement = $this->getImageElementByType($type);
216
        $imageElement->clickLink('Delete');
217
    }
218
219
    public function removeFirstImage()
220
    {
221
        $this->clickTabIfItsNotActive('media');
222
223
        $imageElement = $this->getFirstImageElement();
224
        $imageElement->clickLink('Delete');
225
    }
226
227
    /**
228
     * {@inheritdoc}
229
     */
230
    public function modifyFirstImageType($type)
231
    {
232
        $this->clickTabIfItsNotActive('media');
233
234
        $firstImage = $this->getFirstImageElement();
235
        $this->setImageType($firstImage, $type);
236
    }
237
238
    /**
239
     * {@inheritdoc}
240
     */
241
    public function countImages()
242
    {
243
        $imageElements = $this->getImageElements();
244
245
        return count($imageElements);
246
    }
247
248
    /**
249
     * {@inheritdoc}
250
     */
251
    public function isSlugReadOnlyIn($locale)
252
    {
253
        return 'readonly' === $this->getElement('slug', ['%locale%' => $locale])->getAttribute('readonly');
254
    }
255
256
    /**
257
     * {@inheritdoc}
258
     */
259
    public function associateProducts(ProductAssociationTypeInterface $productAssociationType, array $productsNames)
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $productAssociationType exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
260
    {
261
        $this->clickTab('associations');
262
263
        Assert::isInstanceOf($this->getDriver(), Selenium2Driver::class);
264
265
        $dropdown = $this->getElement('association_dropdown', [
266
            '%association%' => $productAssociationType->getName()
267
        ]);
268
        $dropdown->click();
269
270
        foreach ($productsNames as $productName) {
271
            $dropdown->waitFor(5, function () use ($productName, $productAssociationType) {
272
                return $this->hasElement('association_dropdown_item', [
273
                    '%association%' => $productAssociationType->getName(),
274
                    '%item%' => $productName,
275
                ]);
276
            });
277
278
            $item = $this->getElement('association_dropdown_item', [
279
                '%association%' => $productAssociationType->getName(),
280
                '%item%' => $productName,
281
            ]);
282
            $item->click();
283
        }
284
    }
285
286
    /**
287
     * {@inheritdoc}
288
     */
289
    public function hasAssociatedProduct($productName, ProductAssociationTypeInterface $productAssociationType)
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $productAssociationType exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
290
    {
291
        $this->clickTabIfItsNotActive('associations');
292
293
        return $this->hasElement('association_dropdown_item', [
294
            '%association%' => $productAssociationType->getName(),
295
            '%item%' => $productName,
296
        ]);
297
    }
298
299
    /**
300
     * {@inheritdoc}
301
     */
302
    public function removeAssociatedProduct($productName, ProductAssociationTypeInterface $productAssociationType)
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $productAssociationType exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
303
    {
304
        $this->clickTabIfItsNotActive('associations');
305
306
        $item = $this->getElement('association_dropdown_item_selected', [
307
            '%association%' => $productAssociationType->getName(),
308
            '%item%' => $productName,
309
        ]);
310
311
        $deleteIcon = $item->find('css', 'i.delete');
312
        Assert::notNull($deleteIcon);
313
        $deleteIcon->click();
314
    }
315
316
    /**
317
     * {@inheritdoc}
318
     */
319
    public function getPricingConfigurationForChannelAndCurrencyCalculator(ChannelInterface $channel, CurrencyInterface $currency)
320
    {
321
        $priceConfigurationElement = $this->getElement('pricing_configuration');
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $priceConfigurationElement exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
322
        $priceElement = $priceConfigurationElement
323
            ->find('css', sprintf('label:contains("%s %s")', $channel->getCode(), $currency->getCode()))->getParent();
324
325
        return $priceElement->find('css', 'input')->getValue();
326
    }
327
328
    /**
329
     * {@inheritdoc}
330
     */
331
    public function getSlug($locale)
332
    {
333
        $this->activateLanguageTab($locale);
334
335
        return $this->getElement('slug', ['%locale%' => $locale])->getValue();
336
    }
337
338
    /**
339
     * {@inheritdoc}
340
     */
341
    public function specifySlugIn($slug, $locale)
342
    {
343
        $this->activateLanguageTab($locale);
344
345
        $this->getElement('slug', ['%locale%' => $locale])->setValue($slug);
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351
    public function activateLanguageTab($locale)
352
    {
353
        if (!$this->getDriver() instanceof Selenium2Driver) {
354
            return;
355
        }
356
357
        $languageTabTitle = $this->getElement('language_tab', ['%locale%' => $locale]);
358
        if (!$languageTabTitle->hasClass('active')) {
359
            $languageTabTitle->click();
360
        }
361
    }
362
363
    public function getPriceForChannel($channelName)
364
    {
365
        return $this->getElement('price', ['%channelName%' => $channelName])->getValue();
366
    }
367
368
    /**
369
     * {@inheritdoc}
370
     */
371
    public function getOriginalPriceForChannel($channelName)
372
    {
373
        return $this->getElement('original_price', ['%channelName%' => $channelName])->getValue();
374
    }
375
376
    /**
377
     * {@inheritdoc}
378
     */
379
    protected function getCodeElement()
380
    {
381
        return $this->getElement('code');
382
    }
383
384
    /**
385
     * {@inheritdoc}
386
     */
387
    protected function getElement($name, array $parameters = [])
388
    {
389
        if (!isset($parameters['%locale%'])) {
390
            $parameters['%locale%'] = 'en_US';
391
        }
392
393
        return parent::getElement($name, $parameters);
394
    }
395
396
    /**
397
     * {@inheritdoc}
398
     */
399
    protected function getDefinedElements()
400
    {
401
        return array_merge(parent::getDefinedElements(), [
402
            'association_dropdown' => '.field > label:contains("%association%") ~ .product-select',
403
            'association_dropdown_item' => '.field > label:contains("%association%") ~ .product-select > div.menu > div.item:contains("%item%")',
404
            'association_dropdown_item_selected' => '.field > label:contains("%association%") ~ .product-select > a.label:contains("%item%")',
405
            'attribute' => '.tab[data-tab="%localeCode%"] .attribute .label:contains("%attributeName%") ~ input',
406
            'attribute_delete_button' => '.tab[data-tab="%localeCode%"] .attribute .label:contains("%attributeName%") ~ button',
407
            'code' => '#sylius_product_code',
408
            'images' => '#sylius_product_images',
409
            'language_tab' => '[data-locale="%locale%"] .title',
410
            'locale_tab' => '#attributesContainer .menu [data-tab="%localeCode%"]',
411
            'name' => '#sylius_product_translations_%locale%_name',
412
            'original_price' => '#sylius_product_variant_channelPricings > .field:contains("%channelName%") input[name$="[originalPrice]"]',
413
            'price' => '#sylius_product_variant_channelPricings > .field:contains("%channelName%") input[name$="[price]"]',
414
            'pricing_configuration' => '#sylius_calculator_container',
415
            'main_taxon' => '#sylius_product_mainTaxon',
416
            'slug' => '#sylius_product_translations_%locale%_slug',
417
            'tab' => '.menu [data-tab="%name%"]',
418
            'taxonomy' => 'a[data-tab="taxonomy"]',
419
            'tracked' => '#sylius_product_variant_tracked',
420
            'toggle_slug_modification_button' => '[data-locale="%locale%"] .toggle-product-slug-modification',
421
        ]);
422
    }
423
424
    private function openTaxonBookmarks()
425
    {
426
        $this->getElement('taxonomy')->click();
427
    }
428
429
    /**
430
     * @param string $tabName
431
     */
432
    private function clickTabIfItsNotActive($tabName)
433
    {
434
        $attributesTab = $this->getElement('tab', ['%name%' => $tabName]);
435
        if (!$attributesTab->hasClass('active')) {
436
            $attributesTab->click();
437
        }
438
    }
439
440
    /**
441
     * @param string $tabName
442
     */
443
    private function clickTab($tabName)
444
    {
445
        $attributesTab = $this->getElement('tab', ['%name%' => $tabName]);
446
        $attributesTab->click();
447
    }
448
449
    /**
450
     * @param string $localeCode
451
     */
452
    private function clickLocaleTabIfItsNotActive($localeCode)
453
    {
454
        $localeTab = $this->getElement('locale_tab', ['%localeCode%' => $localeCode]);
455
        if (!$localeTab->hasClass('active')) {
456
            $localeTab->click();
457
        }
458
    }
459
460
    /**
461
     * @param string $type
462
     *
463
     * @return NodeElement
464
     */
465
    private function getImageElementByType($type)
466
    {
467
        $images = $this->getElement('images');
468
        $typeInput = $images->find('css', 'input[value="'.$type.'"]');
469
470
        if (null === $typeInput) {
471
            return null;
472
        }
473
474
        return $typeInput->getParent()->getParent()->getParent();
475
    }
476
477
    /**
478
     * @return NodeElement[]
479
     */
480
    private function getImageElements()
481
    {
482
        $images = $this->getElement('images');
483
484
        return $images->findAll('css', 'div[data-form-collection="item"]');
485
    }
486
487
    /**
488
     * @return NodeElement
489
     */
490
    private function getLastImageElement()
491
    {
492
        $imageElements = $this->getImageElements();
493
494
        Assert::notEmpty($imageElements);
495
496
        return end($imageElements);
497
    }
498
499
    /**
500
     * @return NodeElement
501
     */
502
    private function getFirstImageElement()
503
    {
504
        $imageElements = $this->getImageElements();
505
506
        Assert::notEmpty($imageElements);
507
508
        return reset($imageElements);
509
    }
510
511
    /**
512
     * @param string $locale
513
     */
514
    private function waitForSlugGenerationIfNecessary($locale)
515
    {
516
        if (!$this->getDriver() instanceof Selenium2Driver) {
517
            return;
518
        }
519
520
        $slugElement = $this->getElement('slug', ['%locale%' => $locale]);
521
        if ($slugElement->hasAttribute('readonly')) {
522
            return;
523
        }
524
525
        $value = $slugElement->getValue();
526
        $this->getDocument()->waitFor(10, function () use ($slugElement, $value) {
527
            return $value !== $slugElement->getValue();
528
        });
529
    }
530
531
    /**
532
     * @param NodeElement $imageElement
533
     * @param string $type
534
     */
535
    private function setImageType(NodeElement $imageElement, $type)
536
    {
537
        $typeField = $imageElement->findField('Type');
538
        $typeField->setValue($type);
539
    }
540
}
541