Completed
Push — master ( e8d24b...664609 )
by Sergii
03:19
created

FormContext   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 424
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 7
Bugs 1 Features 3
Metric Value
wmc 47
c 7
b 1
f 3
lcom 1
cbo 8
dl 0
loc 424
rs 8.439

17 Methods

Rating   Name   Duplication   Size   Complexity  
A fillField() 0 4 1
B choseOptionFromAutocompleteVariants() 0 45 6
B fillInWithValueOfFieldOfCurrentUser() 0 22 5
A checkboxAction() 0 8 2
B radioAction() 0 23 6
A fillFields() 0 6 2
A attachFile() 0 16 3
C setValueForHierarchicalSelect() 0 43 7
B shouldSeeThumbnail() 0 26 6
A selectFrom() 0 4 1
A selectFromFollowing() 0 6 2
A assertTextualField() 0 4 1
A assertSelectableField() 0 4 1
A assertCheckableField() 0 4 1
A setDate() 0 4 1
A isDateSelected() 0 4 1
A isDateAvailable() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like FormContext 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 FormContext, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @author Sergii Bondarenko, <[email protected]>
4
 */
5
namespace Drupal\TqExtension\Context\Form;
6
7
// Exceptions.
8
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
9
use Behat\Mink\Exception\ElementNotFoundException;
10
use WebDriver\Exception\NoSuchElement;
11
12
// Helpers.
13
use Behat\Gherkin\Node\TableNode;
14
use WebDriver\Service\CurlService;
15
use Behat\Mink\Element\NodeElement;
16
17
// Utils.
18
use Drupal\TqExtension\Utils\DatePicker;
19
use Drupal\TqExtension\Utils\FormValueAssertion;
20
use Drupal\TqExtension\Utils\EntityDrupalWrapper;
21
22
class FormContext extends RawFormContext
23
{
24
    /**
25
     * @param string $value
26
     *   Typed text.
27
     * @param string $selector
28
     *   Selector of the field.
29
     * @param int $option
30
     *   An option number. Will be selected from loaded variants.
31
     *
32
     * @throws \InvalidArgumentException
33
     *   When $option is less than zero.
34
     * @throws NoSuchElement
35
     *   When autocomplete list was not loaded.
36
     * @throws \RuntimeException
37
     *   When neither option was not loaded.
38
     * @throws \OverflowException
39
     *   When $option is more than variants are available.
40
     * @throws \Exception
41
     *   When value was not changed.
42
     *
43
     * @Then /^(?:|I )typed "([^"]*)" in the "([^"]*)" field and chose (\d+) option from autocomplete variants$/
44
     */
45
    public function choseOptionFromAutocompleteVariants($value, $selector, $option)
46
    {
47
        if (!$option) {
48
            throw new \InvalidArgumentException(sprintf(
49
                'An option that will be chosen expected as positive number, but was got the: %s',
50
                $option
51
            ));
52
        }
53
54
        $field = $this->element('field', $selector);
55
        // Syn - a Standalone Synthetic Event Library, provided by Selenium.
56
        $this->executeJsOnElement($field, sprintf("Syn.type({{ELEMENT}}, '%s')", token_replace($value)));
57
        $this->waitAjaxAndAnimations();
58
59
        $autocomplete = $field->getParent()->findById('autocomplete');
60
        $this->throwNoSuchElementException('#autocomplete', $autocomplete);
61
62
        $options = count($autocomplete->findAll('css', 'li'));
63
64
        if ($options < 1) {
65
            throw new \RuntimeException('Neither option was not loaded.');
66
        }
67
68
        if ($option > $options) {
69
            throw new \OverflowException(sprintf(
70
                'You can not select an option %s, as there are only %d.',
71
                $option,
72
                $options
73
            ));
74
        }
75
76
        for ($i = 0; $i < $option; $i++) {
77
            // 40 - down
78
            $field->keyDown(40);
79
            $field->keyUp(40);
80
        }
81
82
        // 13 - return
83
        $field->keyDown(13);
84
        $field->keyUp(13);
85
86
        if ($field->getValue() == $value) {
87
            throw new \Exception(sprintf('The value of "%s" field was not changed.', $selector));
88
        }
89
    }
90
91
    /**
92
     * Use the current user data for filling fields.
93
     *
94
     * @example
95
     * Then I fill "First name" with value of field "First name" of current user
96
     * And fill "field_last_name[und][0]" with value of field "field_user_last_name" of current user
97
     *
98
     * @param string $field
99
     *   The name of field to fill in. HTML Label, name or ID can be user as selector.
100
     * @param string $user_field
101
     *   The name of field from which the data will taken. Drupal label or machine name can be used as selector.
102
     *
103
     * @throws \InvalidArgumentException
104
     * @throws \UnexpectedValueException
105
     * @throws \Exception
106
     * @throws NoSuchElement
107
     *   When field cannot be found.
108
     *
109
     * @Then /^(?:I )fill "([^"]*)" with value of field "([^"]*)" of current user$/
110
     */
111
    public function fillInWithValueOfFieldOfCurrentUser($field, $user_field)
112
    {
113
        if (!empty($this->user) && !$this->user->uid) {
114
            throw new \Exception('Anonymous user have no fields');
115
        }
116
117
        $entity = new EntityDrupalWrapper('user');
118
        $wrapper = $entity->wrapper($this->user->uid);
119
        $user_field = $entity->getFieldNameByLocator($user_field);
120
121
        if (empty($wrapper->{$user_field})) {
122
            throw new \InvalidArgumentException(sprintf('User entity has no "%s" field.', $user_field));
123
        }
124
125
        $value = $wrapper->{$user_field}->value();
126
127
        if (empty($value)) {
128
            throw new \UnexpectedValueException('The value of "%s" field is empty.', $user_field);
129
        }
130
131
        $this->fillField($field, $value);
132
    }
133
134
    /**
135
     * @param string $action
136
     *   Can be "check" or "uncheck".
137
     * @param TableNode $checkboxes
138
     *   Table with one row of checkboxes selectors.
139
     *
140
     * @example
141
     * I uncheck the boxes:
142
     *   | Consumer Products  |
143
     *   | Financial Services |
144
     *
145
     * @example
146
     * I check the boxes:
147
     *   | Consumer Products  |
148
     *   | Financial Services |
149
     *
150
     * @Given /^(?:|I )(?:|un)check the boxes:/
151
     */
152
    public function checkboxAction($action, TableNode $checkboxes)
153
    {
154
        $minkContext = $this->getMinkContext();
155
156
        foreach ($checkboxes->getRows() as $checkbox) {
157
            $minkContext->{trim($action) . 'Option'}(reset($checkbox));
158
        }
159
    }
160
161
    /**
162
     * This method was defined and used instead of "assertSelectRadioById",
163
     * because the field label can contain too long value and better to use
164
     * another selector instead of label.
165
     *
166
     * @see MinkContext::assertSelectRadioById()
167
     *
168
     * @param string $customized
169
     *   Can be an empty string or " customized".
170
     * @param string $selector
171
     *   Field selector.
172
     *
173
     * @throws NoSuchElement
174
     *   When radio button was not found.
175
     * @throws \Exception
176
     *
177
     * @Given /^(?:|I )check the(| customized) "([^"]*)" radio button$/
178
     */
179
    public function radioAction($customized, $selector)
180
    {
181
        $field = $this->getWorkingElement()->findField($selector);
182
        $customized = (bool) $customized;
183
184
        if ($field !== null && !$customized) {
185
            $field->selectOption($field->getAttribute('value'));
186
            return;
187
        }
188
189
        // Find all labels of a radio button or only first, if it is not custom.
190
        foreach ($this->findLabels($selector) as $label) {
191
            // Check a custom label for visibility.
192
            if ($customized && !$label->isVisible()) {
193
                continue;
194
            }
195
196
            $label->click();
197
            return;
198
        }
199
200
        $this->throwNoSuchElementException($selector, $field);
201
    }
202
203
    /**
204
     * @param string $selector
205
     * @param string $value
206
     *
207
     * @throws NoSuchElement
208
     *
209
     * @When /^(?:|I )fill "([^"]*)" with "([^"]*)"$/
210
     */
211
    public function fillField($selector, $value)
212
    {
213
        $this->element('field', $selector)->setValue(token_replace($value));
214
    }
215
216
    /**
217
     * @param TableNode $fields
218
     *   | Field locator | Value |
219
     *
220
     * @throws NoSuchElement
221
     *
222
     * @When /^(?:|I )fill the following:$/
223
     */
224
    public function fillFields(TableNode $fields)
225
    {
226
        foreach ($fields->getRowsHash() as $field => $value) {
227
            $this->fillField($field, $value);
228
        }
229
    }
230
231
    /**
232
     * @param string $file
233
     *   Path to a file. Relative to the directory specified in "files_path" in behat.yml.
234
     * @param string $selector
235
     *   Field selector (label|id|name).
236
     *
237
     * @throws \Exception
238
     * @throws NoSuchElement
239
     *
240
     * @Given /^(?:|I )attach file "([^"]*)" to "([^"]*)"$/
241
     */
242
    public function attachFile($file, $selector)
243
    {
244
        $filesPath = $this->getMinkParameter('files_path');
245
246
        if (!$filesPath) {
247
            throw new \Exception('The "files_path" Mink parameter was not configured.');
248
        }
249
250
        $file = rtrim(realpath($filesPath), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $file;
251
252
        if (!is_file($file)) {
253
            throw new \InvalidArgumentException(sprintf('The "%s" file does not exist.', $file));
254
        }
255
256
        $this->element('field', $selector)->attachFile($file);
257
    }
258
259
    /**
260
     * @param string $selector
261
     * @param TableNode $values
262
     *
263
     * @throws ElementNotFoundException
264
     * @throws \Exception
265
     * @throws NoSuchElement
266
     *
267
     * @Given /^(?:|I )select the following in "([^"]*)" hierarchical select:$/
268
     */
269
    public function setValueForHierarchicalSelect($selector, TableNode $values)
270
    {
271
        $element = $this->getWorkingElement();
272
        // Try to selects by wrapper ID.
273
        $wrapper = $element->findById($selector);
274
275
        if (null !== $wrapper) {
276
            $labels = $wrapper->findAll('xpath', '//label[@for]');
277
        } else {
278
            $labels = $this->findLabels($selector);
279
        }
280
281
        if (empty($labels)) {
282
            throw new \Exception('No one hierarchical select was found.');
283
        }
284
285
        /** @var NodeElement $label */
286
        $label = reset($labels);
287
        $parent = $label->getParent();
288
289
        foreach (array_keys($values->getRowsHash()) as $i => $value) {
290
            /** @var NodeElement[] $selects */
291
            $selects = [];
292
293
            /** @var NodeElement $select */
294
            foreach ($parent->findAll('css', 'select') as $select) {
295
                if ($select->isVisible()) {
296
                    $selects[] = $select;
297
                }
298
            }
299
300
            if (!isset($selects[$i])) {
301
                throw new \InvalidArgumentException(sprintf(
302
                    'The value "%s" was specified for select "%s" but it does not exist.',
303
                    $value,
304
                    $i
305
                ));
306
            }
307
308
            $selects[$i]->selectOption($value);
309
            $this->waitAjaxAndAnimations();
310
        }
311
    }
312
313
    /**
314
     * Check that an image was uploaded and can be viewed on the page.
315
     *
316
     * @throws \Exception
317
     * @throws FileNotFoundException
318
     *
319
     * @Then /^(?:|I )should see the thumbnail$/
320
     */
321
    public function shouldSeeThumbnail()
322
    {
323
        $thumb = false;
324
325
        foreach (['.upload-preview', '.media-thumbnail img', '.image-preview img'] as $selector) {
326
            if ($thumb) {
327
                break;
328
            }
329
330
            $thumb = $this->findByCss($selector);
331
        }
332
333
        if (null === $thumb) {
334
            throw new \Exception('An expected image tag was not found.');
335
        }
336
337
        $file = explode('?', $thumb->getAttribute('src'));
338
        $file = reset($file);
339
340
        $curl = new CurlService();
341
        list(, $info) = $curl->execute('GET', $file);
342
343
        if (empty($info) || strpos($info['content_type'], 'image/') === false) {
344
            throw new FileNotFoundException(sprintf('%s did not return an image', $file));
345
        }
346
    }
347
348
    /**
349
     * @param string $option
350
     * @param string $selector
351
     *
352
     * @Then /^(?:|I )pick "([^"]*)" from "([^"]*)"$/
353
     */
354
    public function selectFrom($option, $selector)
355
    {
356
        $this->element('*', $selector)->selectOption($option);
357
    }
358
359
    /**
360
     * @example
361
     * And pick the following:
362
     *   | Entity Reference                     | Type of new field    |
363
     *   | Inline entity form - Multiple values | Widget for new field |
364
     *
365
     * @param TableNode $rows
366
     *
367
     * @Then /^(?:|I )pick the following:$/
368
     */
369
    public function selectFromFollowing(TableNode $rows)
370
    {
371
        foreach ($rows->getRowsHash() as $option => $selector) {
372
            $this->selectFrom($option, $selector);
373
        }
374
    }
375
376
    /**
377
     * @example
378
     * And check that "Users" field has "admin" value
379
     * And check that "Users" field has not "customer" value
380
     *
381
     * @Then /^(?:|I )check that "([^"]*)" field has(| not) "([^"]*)" value$/
382
     */
383
    public function assertTextualField($selector, $not, $expected)
384
    {
385
        (new FormValueAssertion($this, $selector, $not, $expected))->textual();
386
    }
387
388
    /**
389
     * @example
390
     * And check that "User" is selected in "Apply to" select
391
     * And check that "Product(s)" is not selected in "Apply to" select
392
     *
393
     * @Then /^(?:|I )check that "([^"]*)" is(| not) selected in "([^"]*)" select$/
394
     */
395
    public function assertSelectableField($expected, $not, $selector)
396
    {
397
        (new FormValueAssertion($this, $selector, $not, $expected))->selectable();
398
    }
399
400
    /**
401
     * @example
402
     * And check that "Order discount" is checked
403
     * And check that "Product discount" is not checked
404
     *
405
     * @Then /^(?:|I )check that "([^"]*)" is(| not) checked$/
406
     */
407
    public function assertCheckableField($selector, $not)
408
    {
409
        (new FormValueAssertion($this, $selector, $not))->checkable();
410
    }
411
412
    /**
413
     * @param string $date
414
     * @param string $selector
415
     *
416
     * @Then /^(?:|I )choose "([^"]*)" in "([^"]*)" datepicker$/
417
     * @Then /^(?:|I )set the "([^"]*)" for "([^"]*)" datepicker$/
418
     */
419
    public function setDate($date, $selector)
420
    {
421
        (new DatePicker($this, $selector, $date))->isDateAvailable()->setDate()->isDateSelected();
422
    }
423
424
    /**
425
     * @param string $selector
426
     * @param string $date
427
     *
428
     * @Then /^(?:|I )check that "([^"]*)" datepicker contains "([^"]*)" date$/
429
     */
430
    public function isDateSelected($selector, $date)
431
    {
432
        (new DatePicker($this, $selector, $date))->isDateSelected();
433
    }
434
435
    /**
436
     * @param string $date
437
     * @param string $selector
438
     *
439
     * @Then /^(?:|I )check that "([^"]*)" is available for "([^"]*)" datepicker$/
440
     */
441
    public function isDateAvailable($date, $selector)
442
    {
443
        (new DatePicker($this, $selector, $date))->isDateAvailable();
444
    }
445
}
446