Completed
Push — locale-in-url ( c8ed20...ec55a8 )
by Kamil
46:19 queued 21:47
created

UpdateSimpleProductPage   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 501
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 53
lcom 1
cbo 10
dl 0
loc 501
rs 7.4757
c 0
b 0
f 0

42 Methods

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