Completed
Push — remove-content-bundle ( fd7705...1b4274 )
by Kamil
18:36
created

UpdateSimpleProductPage   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 395
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 41
c 0
b 0
f 0
lcom 1
cbo 11
dl 0
loc 395
rs 8.2769

32 Methods

Rating   Name   Duplication   Size   Complexity  
A changeImageWithCode() 0 7 1
A removeImageWithCode() 0 7 1
A removeFirstImage() 0 5 1
A countImages() 0 6 1
A isImageCodeDisabled() 0 4 1
A nameItIn() 0 8 1
A specifyPrice() 0 4 1
A getAttributeValue() 0 9 2
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 isImageWithCodeDisplayed() 0 15 2
A attachImage() 0 15 2
A isSlugReadOnly() 0 4 1
A getValidationMessageForImage() 0 13 2
B associateProducts() 0 25 2
A hasAssociatedProduct() 0 9 1
A removeAssociatedProduct() 0 13 1
A getPricingConfigurationForChannelAndCurrencyCalculator() 0 6 1
A getCodeElement() 0 4 1
A getDefinedElements() 0 20 1
A openTaxonBookmarks() 0 4 1
A clickTabIfItsNotActive() 0 7 2
A clickTab() 0 5 1
A getImageElementByCode() 0 11 2
A getImageElements() 0 6 1
A getLastImageElement() 0 8 1
A getFirstImageElement() 0 8 1
A waitForSlugGenerationIfNecessary() 0 16 3

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 Behat\Mink\Exception\ElementNotFoundException;
17
use Sylius\Behat\Behaviour\ChecksCodeImmutability;
18
use Sylius\Behat\Page\Admin\Crud\UpdatePage as BaseUpdatePage;
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
 */
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->getDocument()->fillField(
38
            sprintf('sylius_product_translations_%s_name', $localeCode), $name
39
        );
40
41
        $this->waitForSlugGenerationIfNecessary();
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47
    public function specifyPrice($price)
48
    {
49
        $this->getDocument()->fillField('Price', $price);
50
    }
51
52
    /**
53
     * {@inheritdoc}
54
     */
55
    public function getAttributeValue($attribute)
56
    {
57
        $attributesTab = $this->getElement('tab', ['%name%' => 'attributes']);
58
        if (!$attributesTab->hasClass('active')) {
59
            $attributesTab->click();
60
        }
61
62
        return $this->getElement('attribute', ['%attribute%' => $attribute])->getValue();
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function hasAttribute($attribute)
69
    {
70
        return null !== $this->getDocument()->find('css', sprintf('.attribute .label:contains("%s")', $attribute));
71
    }
72
73
    /**
74
     * {@inheritdoc}
75
     */
76
    public function selectMainTaxon(TaxonInterface $taxon)
77
    {
78
        $this->openTaxonBookmarks();
79
80
        Assert::isInstanceOf($this->getDriver(), Selenium2Driver::class);
81
82
        $this->getDriver()->executeScript(sprintf('$(\'input.search\').val(\'%s\')', $taxon->getName()));
83
        $this->getElement('search')->click();
84
        $this->getElement('search')->waitFor(10,
85
            function () {
86
                return $this->hasElement('search_item_selected');
87
            });
88
        $itemSelected = $this->getElement('search_item_selected');
89
        $itemSelected->click();
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95
    public function isMainTaxonChosen($taxonName)
96
    {
97
        $this->openTaxonBookmarks();
98
99
        return $taxonName === $this->getDocument()->find('css', '.search > .text')->getText();
100
    }
101
102
    public function disableTracking()
103
    {
104
        $this->getElement('tracked')->uncheck();
105
    }
106
107
    public function enableTracking()
108
    {
109
        $this->getElement('tracked')->check();
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function isTracked()
116
    {
117
        return $this->getElement('tracked')->isChecked();
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function isImageWithCodeDisplayed($code)
124
    {
125
        $imageElement = $this->getImageElementByCode($code);
126
127
        if (null === $imageElement) {
128
            return false;
129
        }
130
131
        $imageUrl = $imageElement->find('css', 'img')->getAttribute('src');
132
        $this->getDriver()->visit($imageUrl);
133
        $pageText = $this->getDocument()->getText();
134
        $this->getDriver()->back();
135
136
        return false === stripos($pageText, '404 Not Found');
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142
    public function attachImage($path, $code = null)
143
    {
144
        $this->clickTabIfItsNotActive('media');
145
146
        $filesPath = $this->getParameter('files_path');
147
148
        $this->getDocument()->clickLink('Add');
149
150
        $imageForm = $this->getLastImageElement();
151
        if (null !== $code) {
152
            $imageForm->fillField('Code', $code);
153
        }
154
155
        $imageForm->find('css', 'input[type="file"]')->attachFile($filesPath.$path);
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function changeImageWithCode($code, $path)
162
    {
163
        $filesPath = $this->getParameter('files_path');
164
165
        $imageForm = $this->getImageElementByCode($code);
166
        $imageForm->find('css', 'input[type="file"]')->attachFile($filesPath.$path);
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function removeImageWithCode($code)
173
    {
174
        $this->clickTabIfItsNotActive('media');
175
176
        $imageElement = $this->getImageElementByCode($code);
177
        $imageElement->clickLink('Delete');
178
    }
179
180
    public function removeFirstImage()
181
    {
182
        $imageElement = $this->getFirstImageElement();
183
        $imageElement->clickLink('Delete');
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189
    public function countImages()
190
    {
191
        $imageElements = $this->getImageElements();
192
193
        return count($imageElements);
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199
    public function isImageCodeDisabled()
200
    {
201
        return 'disabled' === $this->getLastImageElement()->findField('Code')->getAttribute('disabled');
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function isSlugReadOnly()
208
    {
209
        return 'readonly' === $this->getElement('slug')->getAttribute('readonly');
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function getValidationMessageForImage()
216
    {
217
        $this->clickTabIfItsNotActive('media');
218
219
        $imageForm = $this->getLastImageElement();
220
221
        $foundElement = $imageForm->find('css', '.sylius-validation-error');
222
        if (null === $foundElement) {
223
            throw new ElementNotFoundException($this->getSession(), 'Tag', 'css', '.sylius-validation-error');
224
        }
225
226
        return $foundElement->getText();
227
    }
228
229
    /**
230
     * {@inheritdoc}
231
     */
232
    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...
233
    {
234
        $this->clickTab('associations');
235
236
        Assert::isInstanceOf($this->getDriver(), Selenium2Driver::class);
237
238
        $dropdown = $this->getElement('association_dropdown', [
239
            '%association%' => $productAssociationType->getName()
240
        ]);
241
        $dropdown->click();
242
        $dropdown->waitFor(10, function () use ($productsNames, $productAssociationType) {
243
            return $this->hasElement('association_dropdown_item', [
244
                '%association%' => $productAssociationType->getName(),
245
                '%item%' => $productsNames[0],
246
            ]);
247
        });
248
249
        foreach ($productsNames as $productName) {
250
            $item = $this->getElement('association_dropdown_item', [
251
                '%association%' => $productAssociationType->getName(),
252
                '%item%' => $productName,
253
            ]);
254
            $item->click();
255
        }
256
    }
257
258
    /**
259
     * {@inheritdoc}
260
     */
261
    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...
262
    {
263
        $this->clickTabIfItsNotActive('associations');
264
265
        return $this->hasElement('association_dropdown_item', [
266
            '%association%' => $productAssociationType->getName(),
267
            '%item%' => $productName,
268
        ]);
269
    }
270
271
    /**
272
     * {@inheritdoc}
273
     */
274
    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...
275
    {
276
        $this->clickTabIfItsNotActive('associations');
277
278
        $item = $this->getElement('association_dropdown_item_selected', [
279
            '%association%' => $productAssociationType->getName(),
280
            '%item%' => $productName,
281
        ]);
282
283
        $deleteIcon = $item->find('css', 'i.delete');
284
        Assert::notNull($deleteIcon);
285
        $deleteIcon->click();
286
    }
287
288
    /**
289
     * {@inheritdoc}
290
     */
291
    public function getPricingConfigurationForChannelAndCurrencyCalculator(ChannelInterface $channel, CurrencyInterface $currency)
292
    {
293
        $priceElement = $this->getElement('pricing_configuration')->find('css', sprintf('label:contains("%s %s")', $channel->getCode(), $currency->getCode()))->getParent();
294
295
        return $priceElement->find('css', 'input')->getValue();
296
    }
297
298
    /**
299
     * {@inheritdoc}
300
     */
301
    protected function getCodeElement()
302
    {
303
        return $this->getElement('code');
304
    }
305
306
    /**
307
     * {@inheritdoc}
308
     */
309
    protected function getDefinedElements()
310
    {
311
        return array_merge(parent::getDefinedElements(), [
312
            'association_dropdown' => '.field > label:contains("%association%") ~ .product-select',
313
            'association_dropdown_item' => '.field > label:contains("%association%") ~ .product-select > div.menu > div.item:contains("%item%")',
314
            'association_dropdown_item_selected' => '.field > label:contains("%association%") ~ .product-select > a.label:contains("%item%")',
315
            'attribute' => '.attribute .label:contains("%attribute%") ~ input',
316
            'code' => '#sylius_product_code',
317
            'images' => '#sylius_product_images',
318
            'name' => '#sylius_product_translations_en_US_name',
319
            'price' => '#sylius_product_variant_price',
320
            'pricing_configuration' => '#sylius_calculator_container',
321
            'search' => '.ui.fluid.search.selection.dropdown',
322
            'search_item_selected' => 'div.menu > div.item.selected',
323
            'slug' => '#sylius_product_translations_en_US_slug',
324
            'tab' => '.menu [data-tab="%name%"]',
325
            'taxonomy' => 'a[data-tab="taxonomy"]',
326
            'tracked' => '#sylius_product_variant_tracked',
327
        ]);
328
    }
329
330
    private function openTaxonBookmarks()
331
    {
332
        $this->getElement('taxonomy')->click();
333
    }
334
335
    /**
336
     * @param string $tabName
337
     */
338
    private function clickTabIfItsNotActive($tabName)
339
    {
340
        $attributesTab = $this->getElement('tab', ['%name%' => $tabName]);
341
        if (!$attributesTab->hasClass('active')) {
342
            $attributesTab->click();
343
        }
344
    }
345
346
    /**
347
     * @param string $tabName
348
     */
349
    private function clickTab($tabName)
350
    {
351
        $attributesTab = $this->getElement('tab', ['%name%' => $tabName]);
352
        $attributesTab->click();
353
    }
354
355
    /**
356
     * @param string $code
357
     *
358
     * @return NodeElement
359
     */
360
    private function getImageElementByCode($code)
361
    {
362
        $images = $this->getElement('images');
363
        $inputCode = $images->find('css', 'input[value="'.$code.'"]');
364
365
        if (null === $inputCode) {
366
            return null;
367
        }
368
369
        return $inputCode->getParent()->getParent()->getParent();
370
    }
371
372
    /**
373
     * @return NodeElement[]
374
     */
375
    private function getImageElements()
376
    {
377
        $images = $this->getElement('images');
378
379
        return $images->findAll('css', 'div[data-form-collection="item"]');
380
    }
381
382
    /**
383
     * @return NodeElement
384
     */
385
    private function getLastImageElement()
386
    {
387
        $imageElements = $this->getImageElements();
388
389
        Assert::notEmpty($imageElements);
390
391
        return end($imageElements);
392
    }
393
394
    /**
395
     * @return NodeElement
396
     */
397
    private function getFirstImageElement()
398
    {
399
        $imageElements = $this->getImageElements();
400
401
        Assert::notEmpty($imageElements);
402
403
        return reset($imageElements);
404
    }
405
406
    private function waitForSlugGenerationIfNecessary()
407
    {
408
        if (!$this->getDriver() instanceof Selenium2Driver) {
409
            return;
410
        }
411
412
        $slugElement = $this->getElement('slug');
413
        if ($slugElement->hasAttribute('readonly')) {
414
            return;
415
        }
416
417
        $value = $slugElement->getValue();
418
        $this->getDocument()->waitFor(10, function () use ($slugElement, $value) {
419
            return $value !== $slugElement->getValue();
420
        });
421
    }
422
}
423