Completed
Pull Request — master (#16)
by Sergii
03:45
created

RawPageContext::findAll()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 0
cts 4
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 1
crap 6
1
<?php
2
/**
3
 * @author Sergii Bondarenko, <[email protected]>
4
 */
5
namespace Drupal\TqExtension\Context;
6
7
// Contexts.
8
use Drupal\DrupalExtension\Context\RawDrupalContext;
9
// Exceptions.
10
use WebDriver\Exception\NoSuchElement;
11
// Helpers.
12
use Behat\Mink\Element\NodeElement;
13
// Utils.
14
use Drupal\TqExtension\Utils\XPath;
15
16
class RawPageContext extends RawDrupalContext
17
{
18
    /**
19
     * @var NodeElement
20
     */
21
    private static $workingElement;
22
23
    /**
24
     * @return NodeElement
25
     */
26
    public function getWorkingElement()
27
    {
28
        if (null === self::$workingElement) {
29
            $this->setWorkingElement($this->getBodyElement());
0 ignored issues
show
Bug introduced by
It seems like $this->getBodyElement() can be null; however, setWorkingElement() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
30
        }
31
32
        return self::$workingElement;
33
    }
34
35
    /**
36
     * @param NodeElement $element
37
     */
38
    public function setWorkingElement(NodeElement $element)
39
    {
40
        self::$workingElement = $element;
41
    }
42
43
    public function unsetWorkingElement()
44
    {
45
        self::$workingElement = null;
46
    }
47
48
    /**
49
     * Find all elements matching CSS selector, name of region from config or inaccurate text.
50
     *
51
     * @param string $selector
52
     *   CSS selector, region name or inaccurate text.
53
     *
54
     * @return NodeElement[]
55
     *   List of nodes.
56
     */
57
    public function findAll($selector)
58
    {
59
        $element = $this->getWorkingElement();
60
61
        $elements = $element
62
            ->findAll($this->computeSelectorType($selector), $selector);
63
64
        if (empty($elements)) {
65
            $elements = $this->inaccurateText('*', $selector, $element)
66
                ->findAll();
67
        }
68
69
        return (array) $elements;
70
    }
71
72
    /**
73
     * @param string $selector
74
     *
75
     * @return NodeElement
76
     */
77
    public function findByCss($selector)
78
    {
79
        return $this->getWorkingElement()
80
            ->find($this->computeSelectorType($selector), $selector);
81
    }
82
83
    /**
84
     * @param string $selector
85
     *
86
     * @return NodeElement|null
87
     */
88
    public function findField($selector)
89
    {
90
        $selector = ltrim($selector, '#');
91
        $element = $this->getWorkingElement();
92
93
        foreach ($this->findLabels($selector) as $forAttribute => $label) {
94
            // We trying to find an ID with "-upload" suffix, because some
95
            // image inputs in Drupal are suffixed by it.
96
            foreach ([$forAttribute, "$forAttribute-upload"] as $elementID) {
97
                $field = $element->findById($elementID);
98
99
                if (null !== $field) {
100
                    return $field;
101
                }
102
            }
103
        }
104
105
        return $element->findField($selector);
106
    }
107
108
    /**
109
     * @param string $selector
110
     *
111
     * @return NodeElement
112
     */
113
    public function findButton($selector)
114
    {
115
        $element = $this->getWorkingElement();
116
117
        // Search inside of: "id", "name", "title", "alt" and "value" attributes.
118
        return $element->findButton($selector) ?: $this->inaccurateText('button', $selector, $element)->find();
119
    }
120
121
    /**
122
     * @param string $text
123
     *
124
     * @return NodeElement|null
125
     */
126
    public function findByText($text)
127
    {
128
        return $this->inaccurateText('*', $text)->find();
129
    }
130
131
    /**
132
     * @param string $locator
133
     *   Element locator. Can be inaccurate text, inaccurate field label, CSS selector or region name.
134
     *
135
     * @throws NoSuchElement
136
     *
137
     * @return NodeElement
138
     */
139
    public function findElement($locator)
140
    {
141
        return $this->findByCss($locator)
142
            ?: $this->findField($locator)
143
                ?: $this->findButton($locator)
144
                    ?: $this->findByText($locator);
145
    }
146
147
    /**
148
     * Find all field labels by text.
149
     *
150
     * @param string $text
151
     *   Label text.
152
     *
153
     * @return NodeElement[]
154
     */
155
    public function findLabels($text)
156
    {
157
        $labels = [];
158
159
        foreach ($this->inaccurateText('label[@for]', $text)->findAll() as $label) {
160
            $labels[$label->getAttribute('for')] = $label;
161
        }
162
163
        return $labels;
164
    }
165
166
    /**
167
     * @return NodeElement
168
     */
169
    public function getBodyElement()
170
    {
171
        return $this->getSession()->getPage()->find('css', 'body');
172
    }
173
174
    /**
175
     * @param string $selector
176
     *   Element selector.
177
     * @param mixed $element
178
     *   Existing element or null.
179
     *
180
     * @throws NoSuchElement
181
     */
182
    public function throwNoSuchElementException($selector, $element)
183
    {
184
        if (null === $element) {
185
            throw new NoSuchElement(sprintf('Cannot find an element by "%s" selector.', $selector));
186
        }
187
    }
188
189
    /**
190
     * @param string $locator
191
     * @param string $selector
192
     *
193
     * @throws \RuntimeException
194
     * @throws NoSuchElement
195
     *
196
     * @return NodeElement
197
     */
198
    public function element($locator, $selector)
199
    {
200
        $map = [
201
            'button' => 'Button',
202
            'field' => 'Field',
203
            'text' => 'ByText',
204
            'css' => 'ByCss',
205
            '*' => 'Element',
206
        ];
207
208
        if (!isset($map[$locator])) {
209
            throw new \RuntimeException(sprintf('Locator "%s" is not available.', $locator));
210
        }
211
212
        $selector = \DrupalKernelPlaceholder::t($selector);
213
        $element = $this->{'find' . $map[$locator]}($selector);
214
        $this->throwNoSuchElementException($selector, $element);
215
216
        return $element;
217
    }
218
219
    /**
220
     * @param string $query
221
     * @param string $text
222
     * @param NodeElement $parent
223
     *
224
     * @return XPath\InaccurateText
225
     */
226
    private function inaccurateText($query, $text, NodeElement $parent = null)
227
    {
228
        return (new XPath\InaccurateText("//$query", $parent ?: $this->getWorkingElement()))->text($text);
229
    }
230
231
    /**
232
     * @param string $selector
233
     *
234
     * @return string
235
     */
236
    private function computeSelectorType($selector)
237
    {
238
        return empty($this->getDrupalParameter('region_map')[$selector]) ? 'css' : 'region';
239
    }
240
}
241