Completed
Push — pull-request/7087 ( f84acd )
by Kamil
24:59
created

UpdateSimpleProductPage   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 540
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 55
c 0
b 0
f 0
lcom 1
cbo 10
dl 0
loc 540
rs 6.8

44 Methods

Rating   Name   Duplication   Size   Complexity  
A nameItIn() 0 7 1
A specifyPrice() 0 4 1
A specifyOriginalPrice() 0 4 1
A addSelectedAttributes() 0 11 1
A removeAttribute() 0 6 1
A getAttributeValue() 0 7 1
A getNumberOfAttributes() 0 4 1
A hasAttribute() 0 4 1
B selectMainTaxon() 0 38 1
A isMainTaxonChosen() 0 6 1
A disableTracking() 0 4 1
A enableTracking() 0 4 1
A isTracked() 0 4 1
A enableSlugModification() 0 4 1
A isImageWithTypeDisplayed() 0 15 2
A attachImage() 0 15 2
A changeImageWithType() 0 7 1
A removeImageWithType() 0 7 1
A removeFirstImage() 0 7 1
A modifyFirstImageType() 0 7 1
A countImages() 0 6 1
A isSlugReadOnlyIn() 0 4 1
B associateProducts() 0 26 2
A hasAssociatedProduct() 0 9 1
A removeAssociatedProduct() 0 13 1
A getPricingConfigurationForChannelAndCurrencyCalculator() 0 8 1
A getSlug() 0 6 1
A specifySlugIn() 0 6 1
A activateLanguageTab() 0 11 3
A getPriceForChannel() 0 4 1
A getOriginalPriceForChannel() 0 4 1
A getCodeElement() 0 4 1
A getElement() 0 8 2
B getDefinedElements() 0 24 1
A openTaxonBookmarks() 0 4 1
A clickTabIfItsNotActive() 0 7 2
A clickTab() 0 5 1
A clickLocaleTabIfItsNotActive() 0 7 2
A getImageElementByType() 0 11 2
A getImageElements() 0 6 1
A getLastImageElement() 0 8 1
A getFirstImageElement() 0 8 1
A waitForSlugGenerationIfNecessary() 0 16 3
A setImageType() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like UpdateSimpleProductPage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use UpdateSimpleProductPage, and based on these observations, apply Extract Interface, too.

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