Completed
Pull Request — master (#16)
by Sergii
07:37
created

RawPageContext::inaccurateText()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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