Completed
Pull Request — master (#16)
by Sergii
04:56
created

RawTqContext::getTqParameter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 1
cp 0
rs 10
cc 2
eloc 2
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 Behat\Behat\Context\SnippetAcceptingContext;
9
use Drupal\DrupalExtension\Context as DrupalContexts;
10
// Exceptions.
11
use Behat\Behat\Context\Exception\ContextNotFoundException;
12
use Behat\DebugExtension\Debugger;
13
// Helpers.
14
use WebDriver\Session;
15
use Drupal\Driver\DrushDriver;
16
use Drupal\Driver\DriverInterface as DrupalDriverInterface;
17
use Drupal\Component\Utility\Random;
18
use Behat\Mink\Element\NodeElement;
19
use Behat\Mink\Driver\GoutteDriver;
20
use Behat\Mink\Driver\Selenium2Driver;
21
use Behat\Mink\Driver\DriverInterface as SessionDriverInterface;
22
use Behat\Behat\Hook\Scope\StepScope;
23
use Behat\Behat\Context\Environment\InitializedContextEnvironment;
24
use Behat\Testwork\Environment\Environment;
25
// Utils.
26
use Drupal\TqExtension\Utils\Url;
27
use Drupal\TqExtension\Utils\Tags;
28
use Drupal\TqExtension\Cores\DrupalKernelPlaceholder;
29
30
/**
31
 * @see RawTqContext::__call()
32
 *
33
 * @method User\UserContext getUserContext()
34
 * @method Node\NodeContext getNodeContext()
35
 * @method Form\FormContext getFormContext()
36
 * @method Email\EmailContext getEmailContext()
37
 * @method Drush\DrushContext getDrushContext()
38
 * @method Wysiwyg\WysiwygContext getWysiwygContext()
39
 * @method Message\MessageContext getMessageContext()
40
 * @method Redirect\RedirectContext getRedirectContext()
41
 * @method TqContext getTqContext()
42
 * @method DrupalContexts\MinkContext getMinkContext()
43
 * @method DrupalContexts\DrupalContext getDrupalContext()
44
 * @method Random getRandom()
45
 */
46
class RawTqContext extends RawPageContext implements TqContextInterface
47
{
48
    use Debugger;
49
    use Tags;
50
51
    /**
52
     * Parameters of TqExtension.
53
     *
54
     * @var array
55
     */
56
    private $parameters = [];
57
    /**
58
     * @var string
59
     */
60
    protected static $pageUrl = '';
61
62
    /**
63
     * @param string $method
64
     * @param array $arguments
65
     *
66
     * @throws \Exception
67
     * @throws ContextNotFoundException
68
     *   When context class cannot be loaded.
69
     *
70
     * @return SnippetAcceptingContext
71
     */
72
    public function __call($method, array $arguments)
73
    {
74
        $environment = $this->getEnvironment();
75
        // @example
76
        // The "getFormContext" method is not declared and his name will be split by capital
77
        // letters, creating an array with three items: "get", "Form" and "Context".
78
        list(, $base, $context) = preg_split('/(?=[A-Z])/', $method);
79
80
        foreach ([
81
            [$this->getTqParameter('namespace'), 'Context', $base],
82
            ['Drupal', 'DrupalExtension', 'Context'],
83
        ] as $class) {
84
            $class[] = "$base$context";
85
            $class = implode('\\', $class);
86
87
            if ($environment->hasContextClass($class)) {
88
                return $environment->getContext($class);
89
            }
90
        }
91
92
        throw new \Exception(sprintf('Method "%s" does not exist', $method));
93
    }
94
95
    /**
96
     * Get selector by name.
97
     *
98
     * @param string $name
99
     *   Selector name from the configuration file.
100
     *
101
     * @return string
102
     *   CSS selector.
103
     *
104
     * @throws \Exception
105
     *   If selector does not exits.
106
     */
107
    public function getDrupalSelector($name)
108
    {
109
        $selectors = $this->getDrupalParameter('selectors');
110
111
        if (!isset($selectors[$name])) {
112
            throw new \Exception(sprintf('No such selector configured: %s', $name));
113
        }
114
115
        return $selectors[$name];
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121
    public function getDrupalText($name)
122
    {
123
        // Make text selectors translatable.
124
        return DrupalKernelPlaceholder::t(parent::getDrupalText($name));
125
    }
126
127
    /**
128
     * @param string $site
129
     *   Drupal site folder.
130
     *
131
     * @return string
132
     *   URL to files directory.
133
     */
134
    public function getFilesUrl($site = 'default')
135
    {
136
        return $this->locatePath("sites/$site/files");
137
    }
138
139
    /**
140
     * @param string $text
141
     *   JS code for processing.
142
     *
143
     * @return self
144
     */
145
    protected function processJavaScript(&$text)
146
    {
147
        $text = str_replace(['$'], ['jQuery'], $text);
148
149
        return $this;
150
    }
151
152
    /**
153
     * @return Environment|InitializedContextEnvironment
154
     */
155
    public function getEnvironment()
156
    {
157
        return $this->getDrupal()->getEnvironment();
158
    }
159
160
    /**
161
     * @return SessionDriverInterface|Selenium2Driver|GoutteDriver
162
     */
163
    public function getSessionDriver()
164
    {
165
        return $this->getSession()->getDriver();
166
    }
167
168
    /**
169
     * @return Session
170
     */
171
    public function getWebDriverSession()
172
    {
173
        return $this->getSessionDriver()->getWebDriverSession();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Mink\Driver\DriverInterface as the method getWebDriverSession() does only exist in the following implementations of said interface: Behat\Mink\Driver\Selenium2Driver.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
174
    }
175
176
    /**
177
     * @todo Remove this when DrupalExtension will be used Mink >=1.6 and use $this->getSession->getWindowNames();
178
     *
179
     * @return string[]
180
     */
181
    public function getWindowNames()
182
    {
183
        return $this->getWebDriverSession()->window_handles();
184
    }
185
186
    /**
187
     * @param NodeElement $element
188
     * @param string $script
189
     *
190
     * @example
191
     * $this->executeJsOnElement($this->element('*', 'Meta tags'), 'return jQuery({{ELEMENT}}).text();');
192
     * $this->executeJsOnElement($this->element('*', '#menu'), '{{ELEMENT}}.focus();');
193
     *
194
     * @throws \Exception
195
     *
196
     * @return mixed
197
     */
198
    public function executeJsOnElement(NodeElement $element, $script)
199
    {
200
        $session = $this->getWebDriverSession();
201
        // We need to trigger something with "withSyn" method, because, otherwise an element won't be found.
202
        $element->focus();
203
204
        $this->processJavaScript($script);
205
        self::debug([$script]);
206
207
        return $session->execute([
208
            'script' => str_replace('{{ELEMENT}}', 'arguments[0]', $script),
209
            'args' => [['ELEMENT' => $session->element('xpath', $element->getXpath())->getID()]],
210
        ]);
211
    }
212
213
    /**
214
     * @param string $javascript
215
     *   JS code for execution.
216
     * @param array $args
217
     *   Placeholder declarations.
218
     *
219
     * @return mixed
220
     */
221
    public function executeJs($javascript, array $args = [])
222
    {
223
        $javascript = DrupalKernelPlaceholder::formatString($javascript, $args);
224
225
        $this->processJavaScript($javascript);
226
        self::debug([$javascript]);
227
228
        return $this->getSession()->evaluateScript($javascript);
229
    }
230
231
    /**
232
     * Check JS events in step definition.
233
     *
234
     * @param StepScope $event
235
     *
236
     * @return int
237
     */
238
    public static function isStepImpliesJsEvent(StepScope $event)
239
    {
240
        return self::hasTag('javascript') && preg_match('/(follow|press|click|submit)/i', $event->getStep()->getText());
241
    }
242
243
    /**
244
     * @return DrupalDriverInterface|DrushDriver
245
     */
246
    public function getDrushDriver()
247
    {
248
        return $this->getDriver('drush');
249
    }
250
251
    /**
252
     * Wait for all AJAX requests and jQuery animations.
253
     */
254
    public function waitAjaxAndAnimations()
255
    {
256
        $script = "!window.__behatAjax && !$(':animated').length && !$.active";
257
258
        static::processJavaScript($script);
259
260
        $this->getSession()->wait(1000, $script);
261
    }
262
263
    /**
264
     * {@inheritdoc}
265
     */
266
    public function setTqParameters(array $parameters)
267
    {
268
        if (empty($this->parameters)) {
269
            $this->parameters = $parameters;
270
        }
271
    }
272
273
    /**
274
     * {@inheritdoc}
275
     */
276
    public function getTqParameter($name)
277
    {
278
        return isset($this->parameters[$name]) ? $this->parameters[$name] : false;
279
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284
    public function locatePath($path = '')
285
    {
286
        return (string) new Url($this->getMinkParameter('base_url'), $path);
287
    }
288
289
    /**
290
     * @return string
291
     *   Absolute URL.
292
     */
293
    public function getCurrentUrl()
294
    {
295
        return $this->locatePath($this->getSession()->getCurrentUrl());
296
    }
297
}
298