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
|
|
|
$mainTaxonElement = $this->getElement('main_taxon')->getParent(); |
109
|
|
|
|
110
|
|
|
$isVisibleScript = sprintf( |
111
|
|
|
'$(document.evaluate("%s", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue).dropdown("is visible")', |
112
|
|
|
$mainTaxonElement->getXpath() |
113
|
|
|
); |
114
|
|
|
$isAnyAsyncActionInProgressScript = sprintf( |
115
|
|
|
'jQuery.active' |
116
|
|
|
); |
117
|
|
|
|
118
|
|
|
$this->getDocument()->waitFor(5, function () use ($isAnyAsyncActionInProgressScript) { |
119
|
|
|
return !(bool) $this->getDriver()->evaluateScript($isAnyAsyncActionInProgressScript); |
120
|
|
|
}); |
121
|
|
|
|
122
|
|
|
$mainTaxonElement->click(); |
123
|
|
|
|
124
|
|
|
$this->getDocument()->waitFor(5, function () use ($isAnyAsyncActionInProgressScript) { |
125
|
|
|
return !(bool) $this->getDriver()->evaluateScript($isAnyAsyncActionInProgressScript); |
126
|
|
|
}); |
127
|
|
|
|
128
|
|
|
$this->getDocument()->waitFor(5, function () use ($isVisibleScript) { |
129
|
|
|
return $this->getDriver()->evaluateScript($isVisibleScript); |
130
|
|
|
}); |
131
|
|
|
|
132
|
|
|
$mainTaxonItemElement = $mainTaxonElement->find('css', sprintf('div.item:contains("%s")', $taxon->getName())); |
133
|
|
|
|
134
|
|
|
$mainTaxonItemElement->click(); |
135
|
|
|
|
136
|
|
|
$this->getDocument()->waitFor(5, function () use ($isVisibleScript) { |
137
|
|
|
return !$this->getDriver()->evaluateScript($isVisibleScript); |
138
|
|
|
}); |
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
|
|
|
$this->getElement('toggle_slug_modification_button', ['%locale%' => $locale])->press(); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* {@inheritdoc} |
179
|
|
|
*/ |
180
|
|
|
public function isImageWithTypeDisplayed($type) |
181
|
|
|
{ |
182
|
|
|
$imageElement = $this->getImageElementByType($type); |
183
|
|
|
|
184
|
|
|
if (null === $imageElement) { |
185
|
|
|
return false; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
$imageUrl = $imageElement->find('css', 'img')->getAttribute('src'); |
189
|
|
|
$this->getDriver()->visit($imageUrl); |
190
|
|
|
$pageText = $this->getDocument()->getText(); |
191
|
|
|
$this->getDriver()->back(); |
192
|
|
|
|
193
|
|
|
return false === stripos($pageText, '404 Not Found'); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* {@inheritdoc} |
198
|
|
|
*/ |
199
|
|
|
public function attachImage($path, $type = null) |
200
|
|
|
{ |
201
|
|
|
$this->clickTabIfItsNotActive('media'); |
202
|
|
|
|
203
|
|
|
$filesPath = $this->getParameter('files_path'); |
204
|
|
|
|
205
|
|
|
$this->getDocument()->clickLink('Add'); |
206
|
|
|
|
207
|
|
|
$imageForm = $this->getLastImageElement(); |
208
|
|
|
if (null !== $type) { |
209
|
|
|
$imageForm->fillField('Type', $type); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
$imageForm->find('css', 'input[type="file"]')->attachFile($filesPath.$path); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* {@inheritdoc} |
217
|
|
|
*/ |
218
|
|
|
public function changeImageWithType($type, $path) |
219
|
|
|
{ |
220
|
|
|
$filesPath = $this->getParameter('files_path'); |
221
|
|
|
|
222
|
|
|
$imageForm = $this->getImageElementByType($type); |
223
|
|
|
$imageForm->find('css', 'input[type="file"]')->attachFile($filesPath.$path); |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* {@inheritdoc} |
228
|
|
|
*/ |
229
|
|
|
public function removeImageWithType($type) |
230
|
|
|
{ |
231
|
|
|
$this->clickTabIfItsNotActive('media'); |
232
|
|
|
|
233
|
|
|
$imageElement = $this->getImageElementByType($type); |
234
|
|
|
$imageElement->clickLink('Delete'); |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
public function removeFirstImage() |
238
|
|
|
{ |
239
|
|
|
$this->clickTabIfItsNotActive('media'); |
240
|
|
|
|
241
|
|
|
$imageElement = $this->getFirstImageElement(); |
242
|
|
|
$imageElement->clickLink('Delete'); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* {@inheritdoc} |
247
|
|
|
*/ |
248
|
|
|
public function modifyFirstImageType($type) |
249
|
|
|
{ |
250
|
|
|
$this->clickTabIfItsNotActive('media'); |
251
|
|
|
|
252
|
|
|
$firstImage = $this->getFirstImageElement(); |
253
|
|
|
$this->setImageType($firstImage, $type); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* {@inheritdoc} |
258
|
|
|
*/ |
259
|
|
|
public function countImages() |
260
|
|
|
{ |
261
|
|
|
$imageElements = $this->getImageElements(); |
262
|
|
|
|
263
|
|
|
return count($imageElements); |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* {@inheritdoc} |
268
|
|
|
*/ |
269
|
|
|
public function isSlugReadOnlyIn($locale) |
270
|
|
|
{ |
271
|
|
|
return 'readonly' === $this->getElement('slug', ['%locale%' => $locale])->getAttribute('readonly'); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* {@inheritdoc} |
276
|
|
|
*/ |
277
|
|
|
public function associateProducts(ProductAssociationTypeInterface $productAssociationType, array $productsNames) |
278
|
|
|
{ |
279
|
|
|
$this->clickTab('associations'); |
280
|
|
|
|
281
|
|
|
Assert::isInstanceOf($this->getDriver(), Selenium2Driver::class); |
282
|
|
|
|
283
|
|
|
$dropdown = $this->getElement('association_dropdown', [ |
284
|
|
|
'%association%' => $productAssociationType->getName() |
285
|
|
|
]); |
286
|
|
|
$dropdown->click(); |
287
|
|
|
|
288
|
|
|
foreach ($productsNames as $productName) { |
289
|
|
|
$dropdown->waitFor(5, function () use ($productName, $productAssociationType) { |
290
|
|
|
return $this->hasElement('association_dropdown_item', [ |
291
|
|
|
'%association%' => $productAssociationType->getName(), |
292
|
|
|
'%item%' => $productName, |
293
|
|
|
]); |
294
|
|
|
}); |
295
|
|
|
|
296
|
|
|
$item = $this->getElement('association_dropdown_item', [ |
297
|
|
|
'%association%' => $productAssociationType->getName(), |
298
|
|
|
'%item%' => $productName, |
299
|
|
|
]); |
300
|
|
|
$item->click(); |
301
|
|
|
} |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* {@inheritdoc} |
306
|
|
|
*/ |
307
|
|
|
public function hasAssociatedProduct($productName, ProductAssociationTypeInterface $productAssociationType) |
308
|
|
|
{ |
309
|
|
|
$this->clickTabIfItsNotActive('associations'); |
310
|
|
|
|
311
|
|
|
return $this->hasElement('association_dropdown_item', [ |
312
|
|
|
'%association%' => $productAssociationType->getName(), |
313
|
|
|
'%item%' => $productName, |
314
|
|
|
]); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* {@inheritdoc} |
319
|
|
|
*/ |
320
|
|
|
public function removeAssociatedProduct($productName, ProductAssociationTypeInterface $productAssociationType) |
321
|
|
|
{ |
322
|
|
|
$this->clickTabIfItsNotActive('associations'); |
323
|
|
|
|
324
|
|
|
$item = $this->getElement('association_dropdown_item_selected', [ |
325
|
|
|
'%association%' => $productAssociationType->getName(), |
326
|
|
|
'%item%' => $productName, |
327
|
|
|
]); |
328
|
|
|
|
329
|
|
|
$deleteIcon = $item->find('css', 'i.delete'); |
330
|
|
|
Assert::notNull($deleteIcon); |
331
|
|
|
$deleteIcon->click(); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* {@inheritdoc} |
336
|
|
|
*/ |
337
|
|
|
public function getPricingConfigurationForChannelAndCurrencyCalculator(ChannelInterface $channel, CurrencyInterface $currency) |
338
|
|
|
{ |
339
|
|
|
$priceConfigurationElement = $this->getElement('pricing_configuration'); |
340
|
|
|
$priceElement = $priceConfigurationElement |
341
|
|
|
->find('css', sprintf('label:contains("%s %s")', $channel->getCode(), $currency->getCode()))->getParent(); |
342
|
|
|
|
343
|
|
|
return $priceElement->find('css', 'input')->getValue(); |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* {@inheritdoc} |
348
|
|
|
*/ |
349
|
|
|
public function getSlug($locale) |
350
|
|
|
{ |
351
|
|
|
$this->activateLanguageTab($locale); |
352
|
|
|
|
353
|
|
|
return $this->getElement('slug', ['%locale%' => $locale])->getValue(); |
|
|
|
|
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
/** |
357
|
|
|
* {@inheritdoc} |
358
|
|
|
*/ |
359
|
|
|
public function specifySlugIn($slug, $locale) |
360
|
|
|
{ |
361
|
|
|
$this->activateLanguageTab($locale); |
362
|
|
|
|
363
|
|
|
$this->getElement('slug', ['%locale%' => $locale])->setValue($slug); |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
/** |
367
|
|
|
* {@inheritdoc} |
368
|
|
|
*/ |
369
|
|
|
public function activateLanguageTab($locale) |
370
|
|
|
{ |
371
|
|
|
if (!$this->getDriver() instanceof Selenium2Driver) { |
372
|
|
|
return; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
$languageTabTitle = $this->getElement('language_tab', ['%locale%' => $locale]); |
376
|
|
|
if (!$languageTabTitle->hasClass('active')) { |
377
|
|
|
$languageTabTitle->click(); |
378
|
|
|
} |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
public function getPriceForChannel($channelName) |
382
|
|
|
{ |
383
|
|
|
return $this->getElement('price', ['%channel%' => $channelName])->getValue(); |
|
|
|
|
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* {@inheritdoc} |
388
|
|
|
*/ |
389
|
|
|
protected function getCodeElement() |
390
|
|
|
{ |
391
|
|
|
return $this->getElement('code'); |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* {@inheritdoc} |
396
|
|
|
*/ |
397
|
|
|
protected function getElement($name, array $parameters = []) |
398
|
|
|
{ |
399
|
|
|
if (!isset($parameters['%locale%'])) { |
400
|
|
|
$parameters['%locale%'] = 'en_US'; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
return parent::getElement($name, $parameters); |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
/** |
407
|
|
|
* {@inheritdoc} |
408
|
|
|
*/ |
409
|
|
|
protected function getDefinedElements() |
410
|
|
|
{ |
411
|
|
|
return array_merge(parent::getDefinedElements(), [ |
412
|
|
|
'association_dropdown' => '.field > label:contains("%association%") ~ .product-select', |
413
|
|
|
'association_dropdown_item' => '.field > label:contains("%association%") ~ .product-select > div.menu > div.item:contains("%item%")', |
414
|
|
|
'association_dropdown_item_selected' => '.field > label:contains("%association%") ~ .product-select > a.label:contains("%item%")', |
415
|
|
|
'attribute' => '.tab[data-tab="%localeCode%"] .attribute .label:contains("%attributeName%") ~ input', |
416
|
|
|
'attribute_delete_button' => '.tab[data-tab="%localeCode%"] .attribute .label:contains("%attributeName%") ~ button', |
417
|
|
|
'code' => '#sylius_product_code', |
418
|
|
|
'images' => '#sylius_product_images', |
419
|
|
|
'language_tab' => '[data-locale="%locale%"] .title', |
420
|
|
|
'locale_tab' => '#attributesContainer .menu [data-tab="%localeCode%"]', |
421
|
|
|
'name' => '#sylius_product_translations_%locale%_name', |
422
|
|
|
'price' => '#sylius_product_variant_channelPricings [data-form-collection="item"]:contains("%channel%") input', |
423
|
|
|
'pricing_configuration' => '#sylius_calculator_container', |
424
|
|
|
'main_taxon' => '#sylius_product_mainTaxon', |
425
|
|
|
'slug' => '#sylius_product_translations_%locale%_slug', |
426
|
|
|
'tab' => '.menu [data-tab="%name%"]', |
427
|
|
|
'taxonomy' => 'a[data-tab="taxonomy"]', |
428
|
|
|
'tracked' => '#sylius_product_variant_tracked', |
429
|
|
|
'toggle_slug_modification_button' => '[data-locale="%locale%"] .toggle-product-slug-modification', |
430
|
|
|
]); |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
private function openTaxonBookmarks() |
434
|
|
|
{ |
435
|
|
|
$this->getElement('taxonomy')->click(); |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* @param string $tabName |
440
|
|
|
*/ |
441
|
|
|
private function clickTabIfItsNotActive($tabName) |
442
|
|
|
{ |
443
|
|
|
$attributesTab = $this->getElement('tab', ['%name%' => $tabName]); |
444
|
|
|
if (!$attributesTab->hasClass('active')) { |
445
|
|
|
$attributesTab->click(); |
446
|
|
|
} |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* @param string $tabName |
451
|
|
|
*/ |
452
|
|
|
private function clickTab($tabName) |
453
|
|
|
{ |
454
|
|
|
$attributesTab = $this->getElement('tab', ['%name%' => $tabName]); |
455
|
|
|
$attributesTab->click(); |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
/** |
459
|
|
|
* @param string $localeCode |
460
|
|
|
*/ |
461
|
|
|
private function clickLocaleTabIfItsNotActive($localeCode) |
462
|
|
|
{ |
463
|
|
|
$localeTab = $this->getElement('locale_tab', ['%localeCode%' => $localeCode]); |
464
|
|
|
if (!$localeTab->hasClass('active')) { |
465
|
|
|
$localeTab->click(); |
466
|
|
|
} |
467
|
|
|
} |
468
|
|
|
|
469
|
|
|
/** |
470
|
|
|
* @param string $type |
471
|
|
|
* |
472
|
|
|
* @return NodeElement |
473
|
|
|
*/ |
474
|
|
|
private function getImageElementByType($type) |
475
|
|
|
{ |
476
|
|
|
$images = $this->getElement('images'); |
477
|
|
|
$typeInput = $images->find('css', 'input[value="'.$type.'"]'); |
478
|
|
|
|
479
|
|
|
if (null === $typeInput) { |
480
|
|
|
return null; |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
return $typeInput->getParent()->getParent()->getParent(); |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
/** |
487
|
|
|
* @return NodeElement[] |
488
|
|
|
*/ |
489
|
|
|
private function getImageElements() |
490
|
|
|
{ |
491
|
|
|
$images = $this->getElement('images'); |
492
|
|
|
|
493
|
|
|
return $images->findAll('css', 'div[data-form-collection="item"]'); |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
/** |
497
|
|
|
* @return NodeElement |
498
|
|
|
*/ |
499
|
|
|
private function getLastImageElement() |
500
|
|
|
{ |
501
|
|
|
$imageElements = $this->getImageElements(); |
502
|
|
|
|
503
|
|
|
Assert::notEmpty($imageElements); |
504
|
|
|
|
505
|
|
|
return end($imageElements); |
|
|
|
|
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
/** |
509
|
|
|
* @return NodeElement |
510
|
|
|
*/ |
511
|
|
|
private function getFirstImageElement() |
512
|
|
|
{ |
513
|
|
|
$imageElements = $this->getImageElements(); |
514
|
|
|
|
515
|
|
|
Assert::notEmpty($imageElements); |
516
|
|
|
|
517
|
|
|
return reset($imageElements); |
|
|
|
|
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
/** |
521
|
|
|
* @param string $locale |
522
|
|
|
*/ |
523
|
|
|
private function waitForSlugGenerationIfNecessary($locale) |
524
|
|
|
{ |
525
|
|
|
if (!$this->getDriver() instanceof Selenium2Driver) { |
526
|
|
|
return; |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
$slugElement = $this->getElement('slug', ['%locale%' => $locale]); |
530
|
|
|
if ($slugElement->hasAttribute('readonly')) { |
531
|
|
|
return; |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
$value = $slugElement->getValue(); |
535
|
|
|
$this->getDocument()->waitFor(10, function () use ($slugElement, $value) { |
536
|
|
|
return $value !== $slugElement->getValue(); |
537
|
|
|
}); |
538
|
|
|
} |
539
|
|
|
|
540
|
|
|
/** |
541
|
|
|
* @param NodeElement $imageElement |
542
|
|
|
* @param string $type |
543
|
|
|
*/ |
544
|
|
|
private function setImageType(NodeElement $imageElement, $type) |
545
|
|
|
{ |
546
|
|
|
$typeField = $imageElement->findField('Type'); |
547
|
|
|
$typeField->setValue($type); |
548
|
|
|
} |
549
|
|
|
} |
550
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.