CmsUiContext::theIFillInTheDropdownWith()   F
last analyzed

Complexity

Conditions 16
Paths 577

Size

Total Lines 99
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 57
nc 577
nop 2
dl 0
loc 99
rs 1.9875
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\Framework\Tests\Behaviour;
4
5
use Behat\Behat\Context\Context;
0 ignored issues
show
Bug introduced by
The type Behat\Behat\Context\Context was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Behat\Behat\Hook\Scope\AfterStepScope;
0 ignored issues
show
Bug introduced by
The type Behat\Behat\Hook\Scope\AfterStepScope was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use Behat\Mink\Element\Element;
0 ignored issues
show
Bug introduced by
The type Behat\Mink\Element\Element was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Behat\Mink\Element\NodeElement;
0 ignored issues
show
Bug introduced by
The type Behat\Mink\Element\NodeElement was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use Behat\Mink\Selector\Xpath\Escaper;
0 ignored issues
show
Bug introduced by
The type Behat\Mink\Selector\Xpath\Escaper was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use Behat\Mink\Session;
0 ignored issues
show
Bug introduced by
The type Behat\Mink\Session was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
use SilverStripe\BehatExtension\Context\MainContextAwareTrait;
0 ignored issues
show
Bug introduced by
The type SilverStripe\BehatExtens...t\MainContextAwareTrait was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use SilverStripe\BehatExtension\Utility\StepHelper;
0 ignored issues
show
Bug introduced by
The type SilverStripe\BehatExtension\Utility\StepHelper was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
14
/**
15
 * CmsUiContext
16
 *
17
 * Context used to define steps related to SilverStripe CMS UI like Tree or Panel.
18
 */
19
class CmsUiContext implements Context
20
{
21
    use MainContextAwareTrait;
22
    use StepHelper;
23
24
    /**
25
     * Get Mink session from MinkContext
26
     *
27
     * @param string $name
28
     * @return Session
29
     */
30
    public function getSession($name = null)
31
    {
32
        return $this->getMainContext()->getSession($name);
33
    }
34
35
    /**
36
     * Wait until CMS loading overlay isn't present.
37
     * This is an addition to the "ajax steps" logic in
38
     * SilverStripe\BehatExtension\Context\BasicContext
39
     * which also waits for any ajax requests to finish before continuing.
40
     *
41
     * The check also applies in when not in the CMS, which is a structural issue:
42
     * Every step could cause the CMS to be loaded, and we don't know if we're in the
43
     * CMS UI until we run a check.
44
     *
45
     * Excluding scenarios with @modal tag is required,
46
     * because modal dialogs stop any JS interaction
47
     *
48
     * @AfterStep
49
     * @param AfterStepScope $event
50
     */
51
    public function handleCmsLoadingAfterStep(AfterStepScope $event)
52
    {
53
        // Manually exclude @modal
54
        if ($this->stepHasTag($event, 'modal')) {
55
            return;
56
        }
57
58
        $timeoutMs = $this->getMainContext()->getAjaxTimeout();
59
        $this->getSession()->wait(
60
            $timeoutMs,
61
            "(" .
62
            "document.getElementsByClassName('cms-content-loading-overlay').length +" .
63
            "document.getElementsByClassName('cms-loading-container').length" .
64
            ") == 0"
65
        );
66
    }
67
68
    /**
69
     * @Then /^I should see the CMS$/
70
     */
71
    public function iShouldSeeTheCms()
72
    {
73
        $page = $this->getSession()->getPage();
74
        $cms_element = $page->find('css', '.cms');
75
        assertNotNull($cms_element, 'CMS not found');
76
    }
77
78
    /**
79
     * @Then /^I should see a "([^"]*)" notice$/
80
     */
81
    public function iShouldSeeANotice($notice)
82
    {
83
        $this->getMainContext()->assertElementContains('.notice-wrap', $notice);
84
    }
85
86
    /**
87
     * @Then /^I should see a "([^"]*)" message$/
88
     */
89
    public function iShouldSeeAMessage($message)
90
    {
91
        $page = $this->getMainContext()->getSession()->getPage();
92
        if ($page->find('css', '.message')) {
93
            $this->getMainContext()->assertElementContains('.message', $message);
94
        } else {
95
            // Support for new Bootstrap alerts
96
            $this->getMainContext()->assertElementContains('.alert', $message);
97
        }
98
    }
99
100
    protected function getCmsTabsElement()
101
    {
102
        $this->getSession()->wait(
103
            5000,
104
            "window.jQuery && window.jQuery('.cms-content-header-tabs').size() > 0"
105
        );
106
107
        $page = $this->getSession()->getPage();
108
        $cms_content_header_tabs = $page->find('css', '.cms-content-header-tabs');
109
        assertNotNull($cms_content_header_tabs, 'CMS tabs not found');
110
111
        return $cms_content_header_tabs;
112
    }
113
114
    protected function getCmsContentToolbarElement()
115
    {
116
        $this->getSession()->wait(
117
            5000,
118
            "window.jQuery && window.jQuery('.cms-content-toolbar').size() > 0 "
119
            . "&& window.jQuery('.cms-content-toolbar').children().size() > 0"
120
        );
121
122
        $page = $this->getSession()->getPage();
123
        $cms_content_toolbar_element = $page->find('css', '.cms-content-toolbar');
124
        assertNotNull($cms_content_toolbar_element, 'CMS content toolbar not found');
125
126
        return $cms_content_toolbar_element;
127
    }
128
129
    protected function getCmsTreeElement()
130
    {
131
        $this->getSession()->wait(
132
            5000,
133
            "window.jQuery && window.jQuery('.cms-tree').size() > 0"
134
        );
135
136
        $page = $this->getSession()->getPage();
137
        $cms_tree_element = $page->find('css', '.cms-tree');
138
        assertNotNull($cms_tree_element, 'CMS tree not found');
139
140
        return $cms_tree_element;
141
    }
142
143
    /**
144
     * @Given /^I should see a "([^"]*)" button in CMS Content Toolbar$/
145
     */
146
    public function iShouldSeeAButtonInCmsContentToolbar($text)
147
    {
148
        $cms_content_toolbar_element = $this->getCmsContentToolbarElement();
149
150
        $element = $cms_content_toolbar_element->find('named', array('link_or_button', "'$text'"));
151
        assertNotNull($element, sprintf('%s button not found', $text));
152
    }
153
154
    /**
155
     * @When /^I should see "([^"]*)" in the tree$/
156
     */
157
    public function stepIShouldSeeInCmsTree($text)
158
    {
159
        // Wait until visible
160
        $cmsTreeElement = $this->getCmsTreeElement();
161
        $element = $cmsTreeElement->find('named', array('content', "'$text'"));
162
        assertNotNull($element, sprintf('%s not found', $text));
163
    }
164
165
    /**
166
     * @When /^I should not see "([^"]*)" in the tree$/
167
     */
168
    public function stepIShouldNotSeeInCmsTree($text)
169
    {
170
        // Wait until not visible
171
        $cmsTreeElement = $this->getCmsTreeElement();
172
        $element = $cmsTreeElement->find('named', array('content', "'$text'"));
173
        assertNull($element, sprintf('%s found', $text));
174
    }
175
176
    /**
177
     * @When /^I should (|not )see "([^"]*)" in the cms list$/
178
     */
179
    public function stepIShouldSeeInCmsList($negate, $text)
180
    {
181
        // Wait until visible
182
        $this->getSession()->wait(
183
            5000,
184
            "document.querySelector('.cms-lists') !== null"
185
        );
186
        $page = $this->getSession()->getPage();
187
        $cmsListElement = $page->find('css', '.cms-list');
188
        assertNotNull($cmsListElement, 'CMS list not found');
189
190
        // Check text within this element
191
        $element = $cmsListElement->find('named', array('content', "'$text'"));
192
        if (strstr($negate, 'not')) {
193
            assertNull($element, sprintf('Unexpected %s found in cms list', $text));
194
        } else {
195
            assertNotNull($element, sprintf('Expected %s not found in cms list', $text));
196
        }
197
    }
198
199
    /**
200
     * @When /^I should see a "([^"]*)" tab in the CMS content header tabs$/
201
     */
202
    public function stepIShouldSeeInCMSContentTabs($text)
203
    {
204
        // Wait until visible
205
        assertNotNull($this->getCmsTabElement($text), sprintf('%s content tab not found', $text));
206
    }
207
208
    /**
209
     * Applies a specific action to an element
210
     *
211
     * @param NodeElement $element Element to act on
212
     * @param string $action Action, which may be one of 'hover', 'double click', 'right click', or 'left click'
213
     * The default 'click' behaves the same as left click
214
     */
215
    protected function interactWithElement($element, $action = 'click')
216
    {
217
        switch ($action) {
218
            case 'hover':
219
                $element->mouseOver();
220
                break;
221
            case 'double click':
222
                $element->doubleClick();
223
                break;
224
            case 'right click':
225
                $element->rightClick();
226
                break;
227
            case 'left click':
228
            case 'click':
229
            default:
230
                $element->click();
231
                break;
232
        }
233
    }
234
235
    /**
236
     * @When /^I (?P<method>(?:(?:double |right |left |)click)|hover) on "(?P<link>[^"]*)" in the context menu/
237
     */
238
    public function stepIClickOnElementInTheContextMenu($method, $link)
239
    {
240
        $context = $this->getMainContext();
241
        // Wait until context menu has appeared
242
        $this->getSession()->wait(
243
            1000,
244
            "window.jQuery && window.jQuery('.jstree-apple-context').size() > 0"
245
        );
246
        $regionObj = $context->getRegionObj('.jstree-apple-context');
247
        assertNotNull($regionObj, "Context menu could not be found");
248
249
        $linkObj = $regionObj->findLink($link);
250
        if (empty($linkObj)) {
251
            throw new \Exception(sprintf(
252
                'The link "%s" was not found in the context menu on the page %s',
253
                $link,
254
                $this->getSession()->getCurrentUrl()
255
            ));
256
        }
257
258
        $this->interactWithElement($linkObj, $method);
259
    }
260
261
    /**
262
     * @When /^I (?P<method>(?:(?:double |right |left |)click)|hover) on "(?P<text>[^"]*)" in the tree$/
263
     */
264
    public function stepIClickOnElementInTheTree($method, $text)
265
    {
266
        $treeEl = $this->getCmsTreeElement();
267
        $treeNode = $treeEl->findLink($text);
268
        assertNotNull($treeNode, sprintf('%s not found', $text));
269
        $this->interactWithElement($treeNode, $method);
270
    }
271
272
    /**
273
     * @When /^I (?P<method>(?:(?:double |right |left |)click)|hover) on "(?P<text>[^"]*)" in the header tabs$/
274
     */
275
    public function stepIClickOnElementInTheHeaderTabs($method, $text)
276
    {
277
        $tabsNode = $this->getCmsTabElement($text);
278
        assertNotNull($tabsNode, sprintf('%s not found', $text));
279
        $this->interactWithElement($tabsNode, $method);
280
    }
281
282
    /**
283
     * @Then the :text header tab should be active
284
     */
285
    public function theHeaderTabShouldBeActive($text)
286
    {
287
        $element = $this->getCmsTabElement($text);
288
        assertNotNull($element);
289
        assertTrue($element->hasClass('active'));
290
    }
291
292
    /**
293
     * @Then the :text header tab should not be active
294
     */
295
    public function theHeaderTabShouldNotBeActive($text)
296
    {
297
        $element = $this->getCmsTabElement($text);
298
        assertNotNull($element);
299
        assertFalse($element->hasClass('active'));
300
    }
301
302
    /**
303
     * @return NodeElement
304
     */
305
    protected function getCmsTabElement($text)
306
    {
307
        return $this->getCmsTabsElement()->findLink($text);
308
    }
309
310
    /**
311
     * @When /^I expand the "([^"]*)" CMS Panel$/
312
     */
313
    public function iExpandTheCmsPanel()
314
    {
315
        //Tries to find the first visiable toggle in the page
316
        $page = $this->getSession()->getPage();
317
        $toggle_elements = $page->findAll('css', '.toggle-expand');
318
        assertNotNull($toggle_elements, 'Panel toggle not found');
319
        /** @var NodeElement $toggle */
320
        foreach ($toggle_elements as $toggle) {
321
            if ($toggle->isVisible()) {
322
                $toggle->click();
323
            }
324
        }
325
    }
326
327
    /**
328
     * @When /^I (expand|collapse) the content filters$/
329
     */
330
    public function iExpandTheContentFilters($action)
331
    {
332
        $page = $this->getSession()->getPage();
333
        $filterButton = $page->find('css', '.search-box__filter-trigger');
334
        assertNotNull($filterButton, sprintf('Filter button link not found'));
335
336
        $filterButtonExpanded = $filterButton->getAttribute('aria-expanded');
337
338
        if ($action === 'expand') {
339
            if ($filterButtonExpanded === false) {
340
                $filterButton->click();
341
            }
342
        } else {
343
            if ($filterButtonExpanded === true) {
344
                $filterButton->click();
345
            }
346
        }
347
348
        $this->getSession()->wait(2000, 'window.jQuery(".cms-content-filters:animated").length === 0');
349
350
        // If activating, wait until chosen is activated
351
        if ($action === 'expand') {
352
            $this->getSession()->wait(
353
                2000,
354
                <<<'SCRIPT'
355
(window.jQuery(".cms-content-filters select").length === 0) ||
356
(window.jQuery(".cms-content-filters select:visible.has-chosen").length > 0)
357
SCRIPT
358
            );
359
        }
360
    }
361
362
    /**
363
     * @Given /^I press the "([^"]*)" key in the "([^"]*)" field$/
364
     */
365
    public function iPressTheKeyInTheField($key, $field)
366
    {
367
        $this->getSession()->evaluateScript(sprintf(
368
            "jQuery('[name=\"%s\"]')[0].dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: \"%s\" }));
369
            jQuery('[name=\"%s\"]')[0].dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, key: \"%s\" }));",
370
            $field,
371
            $key,
372
            $field,
373
            $key
374
        ));
375
    }
376
377
    /**
378
     * @When /^I (expand|collapse) "([^"]*)" in the tree$/
379
     */
380
    public function iExpandInTheTree($action, $nodeText)
381
    {
382
        //Tries to find the first visiable matched Node in the page
383
        $treeEl = $this->getCmsTreeElement();
384
        $treeNode = $treeEl->findLink($nodeText);
385
        assertNotNull($treeNode, sprintf('%s link not found', $nodeText));
386
        $cssIcon = $treeNode->getParent()->getAttribute("class");
387
        if ($action == "expand") {
388
            //ensure it is collapsed
389
            if (false === strpos($cssIcon, 'jstree-open')) {
390
                $nodeIcon = $treeNode->getParent()->find('css', '.jstree-icon');
391
                assertTrue($nodeIcon->isVisible(), "CMS node '$nodeText' not found");
392
                $nodeIcon->click();
393
            }
394
        } else {
395
            //ensure it is expanded
396
            if (false === strpos($cssIcon, 'jstree-closed')) {
397
                $nodeIcon = $treeNode->getParent()->find('css', '.jstree-icon');
398
                assertTrue($nodeIcon->isVisible(), "CMS node '$nodeText' not found");
399
                $nodeIcon->click();
400
            }
401
        }
402
    }
403
404
    /**
405
     * @When /^I should (not |)see a "([^"]*)" CMS tab$/
406
     */
407
    public function iShouldSeeACmsTab($negate, $tab)
408
    {
409
        $this->getSession()->wait(
410
            5000,
411
            "window.jQuery && window.jQuery('.ui-tabs-nav').size() > 0"
412
        );
413
414
        $page = $this->getSession()->getPage();
415
        $tabsets = $page->findAll('css', '.ui-tabs-nav');
416
        assertNotNull($tabsets, 'CMS tabs not found');
417
418
        $tab_element = null;
419
        /** @var NodeElement $tabset */
420
        foreach ($tabsets as $tabset) {
421
            $tab_element = $tabset->find('named', array('link_or_button', "'$tab'"));
422
            if ($tab_element) {
423
                break;
424
            }
425
        }
426
        if ($negate) {
427
            assertNull($tab_element, sprintf('%s tab found', $tab));
428
        } else {
429
            assertNotNull($tab_element, sprintf('%s tab not found', $tab));
430
        }
431
    }
432
433
    /**
434
     * @When /^I click the "([^"]*)" CMS tab$/
435
     */
436
    public function iClickTheCmsTab($tab)
437
    {
438
        $this->getSession()->wait(
439
            5000,
440
            "window.jQuery && window.jQuery('.ui-tabs-nav').size() > 0"
441
        );
442
443
        $page = $this->getSession()->getPage();
444
        $tabsets = $page->findAll('css', '.ui-tabs-nav');
445
        assertNotNull($tabsets, 'CMS tabs not found');
446
447
        $tab_element = null;
448
        /** @var NodeElement $tabset */
449
        foreach ($tabsets as $tabset) {
450
            if ($tab_element) {
451
                continue;
452
            }
453
            $tab_element = $tabset->find('named', array('link_or_button', "'$tab'"));
454
        }
455
        assertNotNull($tab_element, sprintf('%s tab not found', $tab));
456
457
        $tab_element->click();
458
    }
459
460
    /**
461
     * @Then /^I can see the preview panel$/
462
     */
463
    public function iCanSeeThePreviewPanel()
464
    {
465
        $this->getMainContext()->assertElementOnPage('.cms-preview');
466
    }
467
468
    /**
469
     * @Given /^the preview contains "([^"]*)"$/
470
     */
471
    public function thePreviewContains($content)
472
    {
473
        // see https://groups.google.com/forum/#!topic/behat/QNhOuGHKEWI
474
        $this->getSession()->switchToIFrame('cms-preview-iframe');
475
        $this->getMainContext()->assertPageContainsText($content);
476
        $this->getSession()->switchToWindow();
477
    }
478
479
    /**
480
     * @Given /^I set the CMS mode to "([^"]*)"$/
481
     */
482
    public function iSetTheCmsToMode($mode)
483
    {
484
        $this->theIFillInTheDropdownWith('Change view mode', $mode);
485
        sleep(1);
486
    }
487
488
    /**
489
     * @Given /^I wait for the preview to load$/
490
     */
491
    public function iWaitForThePreviewToLoad()
492
    {
493
        // see https://groups.google.com/forum/#!topic/behat/QNhOuGHKEWI
494
        $this->getSession()->switchToIFrame('cms-preview-iframe');
495
        $this->getSession()->wait(
496
            5000,
497
            "window.jQuery && !window.jQuery('iframe[name=cms-preview-iframe]').hasClass('loading')"
498
        );
499
        $this->getSession()->switchToWindow();
500
    }
501
502
    /**
503
     * @Given /^I switch the preview to "([^"]*)"$/
504
     */
505
    public function iSwitchThePreviewToMode($mode)
506
    {
507
        $controls = $this->getSession()->getPage()->find('css', '.cms-preview-controls');
508
        assertNotNull($controls, 'Preview controls not found');
509
510
        $label = $controls->find('xpath', sprintf(
511
            './/*[count(*)=0 and contains(text(), \'%s\')]',
512
            $mode
513
        ));
514
        assertNotNull($label, 'Preview mode switch not found');
515
516
        $label->click();
517
518
        $this->iWaitForThePreviewToLoad();
519
    }
520
521
    /**
522
     * @Given /^the preview does not contain "([^"]*)"$/
523
     */
524
    public function thePreviewDoesNotContain($content)
525
    {
526
        // see https://groups.google.com/forum/#!topic/behat/QNhOuGHKEWI
527
        $this->getSession()->switchToIFrame('cms-preview-iframe');
528
        $this->getMainContext()->assertPageNotContainsText($content);
529
        $this->getSession()->switchToWindow();
530
    }
531
532
    /**
533
     * When I follow "my link" in preview
534
     *
535
     * @When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)" in preview$/
536
     */
537
    public function clickLinkInPreview($link)
538
    {
539
        // TODO Remove once we have native support in Mink and php-webdriver,
540
        // see https://groups.google.com/forum/#!topic/behat/QNhOuGHKEWI
541
        $this->getSession()->switchToIFrame('cms-preview-iframe');
542
        $link = $this->fixStepArgument($link);
543
        $this->getSession()->getPage()->clickLink($link);
544
        $this->getSession()->switchToWindow();
545
    }
546
547
    /**
548
     * When I press "submit" in preview
549
     *
550
     * @When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)" in preview$/
551
     */
552
    public function pressButtonInPreview($button)
553
    {
554
        // see https://groups.google.com/forum/#!topic/behat/QNhOuGHKEWI
555
        $this->getSession()->switchToIFrame('cms-preview-iframe');
556
        $button = $this->fixStepArgument($button);
557
        $this->getSession()->getPage()->pressButton($button);
558
        $this->getSession()->switchToWindow();
559
    }
560
561
    /**
562
     * Workaround for chosen.js dropdowns or tree dropdowns which hide the original dropdown field.
563
     *
564
     * @When /^(?:|I )fill in the "(?P<field>(?:[^"]|\\")*)" dropdown with "(?P<value>(?:[^"]|\\")*)"$/
565
     * @When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for the "(?P<field>(?:[^"]|\\")*)" dropdown$/
566
     */
567
    public function theIFillInTheDropdownWith($field, $value)
568
    {
569
        $field = $this->fixStepArgument($field);
570
        $value = $this->fixStepArgument($value);
571
572
        $escaper = new Escaper();
573
        $nativeField = $this->getSession()->getPage()->find(
574
            'named',
575
            array('select', $escaper->escapeLiteral($field))
576
        );
577
        if ($nativeField && $nativeField->isVisible()) {
578
            $nativeField->selectOption($value);
579
            return;
580
        }
581
582
        // Given the fuzzy matching, we might get more than one matching field.
583
        $formFields = array();
584
585
        // Find by label
586
        $formField = $this->getSession()->getPage()->findField($field);
587
        if ($formField && $formField->getTagName() == 'select') {
588
            $formFields[] = $formField;
589
        }
590
591
        // Fall back to finding by title (for dropdowns without a label)
592
        if (!$formFields) {
593
            $formFields = $this->getSession()->getPage()->findAll(
594
                'xpath',
595
                sprintf(
596
                    '//*[self::select][(./@title="%s")]',
597
                    $field
598
                )
599
            );
600
        }
601
602
        // Find by name (incl. hidden fields)
603
        if (!$formFields) {
604
            $formFields = $this->getSession()->getPage()->findAll('xpath', "//*[@name='$field']");
605
        }
606
607
        // Find by label
608
        if (!$formFields) {
609
            $label = $this->getSession()->getPage()->find('xpath', "//label[.='$field']");
610
            if ($label && $for = $label->getAttribute('for')) {
611
                $formField = $this->getSession()->getPage()->find('xpath', "//*[@id='$for']");
612
                if ($formField) {
613
                    $formFields[] = $formField;
614
                }
615
            }
616
        }
617
618
        assertGreaterThan(0, count($formFields), sprintf(
619
            'Chosen.js dropdown named "%s" not found',
620
            $field
621
        ));
622
623
        // Traverse up to field holder
624
        /** @var NodeElement $container */
625
        $container = null;
626
        foreach ($formFields as $formField) {
627
            $container = $this->findParentByClass($formField, 'field');
628
            if ($container) {
629
                break; // Default to first visible container
630
            }
631
        }
632
633
        assertNotNull($container, 'Chosen.js field container not found');
634
635
        // Click on newly expanded list element, indirectly setting the dropdown value
636
        $linkEl = $container->find('xpath', './/a');
637
        assertNotNull($linkEl, 'Chosen.js link element not found');
638
        $this->getSession()->wait(100); // wait for dropdown overlay to appear
639
        $linkEl->click();
640
641
        if (in_array('treedropdown', explode(' ', $container->getAttribute('class')))) {
642
            // wait for ajax dropdown to load
643
            $this->getSession()->wait(
644
                5000,
645
                "window.jQuery && "
646
                . "window.jQuery('#" . $container->getAttribute('id') . " .treedropdownfield-panel li').length > 0"
647
            );
648
        } else {
649
            // wait for dropdown overlay to appear (might be animated)
650
            $this->getSession()->wait(300);
651
        }
652
653
        $listEl = $container->find('xpath', sprintf('.//li[contains(normalize-space(string(.)), \'%s\')]', $value));
654
        if (null === $listEl) {
655
            throw new \InvalidArgumentException(sprintf(
656
                'Chosen.js list element with title "%s" not found',
657
                $value
658
            ));
659
        }
660
661
        $listLinkEl = $listEl->find('xpath', './/a');
662
        if ($listLinkEl) {
663
            $listLinkEl->click();
664
        } else {
665
            $listEl->click();
666
        }
667
    }
668
669
    /**
670
     * Returns fixed step argument (with \\" replaced back to ").
671
     *
672
     * @param string $argument
673
     *
674
     * @return string
675
     */
676
    protected function fixStepArgument($argument)
677
    {
678
        return str_replace('\\"', '"', $argument);
679
    }
680
681
    /**
682
     * Returns the closest parent element having a specific class attribute.
683
     *
684
     * @param  NodeElement $el
685
     * @param  String  $class
686
     * @return Element|null
687
     */
688
    protected function findParentByClass(NodeElement $el, $class)
689
    {
690
        $container = $el->getParent();
691
        while ($container && $container->getTagName() != 'body') {
692
            if ($container->isVisible() && in_array($class, explode(' ', $container->getAttribute('class')))) {
693
                return $container;
694
            }
695
            $container = $container->getParent();
696
        }
697
698
        return null;
699
    }
700
}
701