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

RawTqContext   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 243
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 13

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 24
lcom 2
cbo 13
dl 0
loc 243
ccs 0
cts 73
cp 0
rs 10
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getEnvironment() 0 4 1
A getSessionDriver() 0 4 1
A getWebDriverSession() 0 4 1
A getWindowNames() 0 4 1
B __call() 0 27 4
A getDrupalSelector() 0 10 2
A getDrupalText() 0 5 1
A getFilesUrl() 0 4 1
A executeJsOnElement() 0 13 1
A executeJs() 0 8 1
A isStepImpliesJsEvent() 0 4 2
A getDrushDriver() 0 4 1
A waitAjaxAndAnimations() 0 8 1
A setTqParameters() 0 6 2
A getTqParameter() 0 4 2
A locatePath() 0 4 1
A getCurrentUrl() 0 4 1
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\Utils\JavaScript;
29
use Drupal\TqExtension\Cores\DrupalKernelPlaceholder;
30
31
/**
32
 * @see RawTqContext::__call()
33
 *
34
 * @method User\UserContext getUserContext()
35
 * @method Node\NodeContext getNodeContext()
36
 * @method Form\FormContext getFormContext()
37
 * @method Email\EmailContext getEmailContext()
38
 * @method Drush\DrushContext getDrushContext()
39
 * @method Wysiwyg\WysiwygContext getWysiwygContext()
40
 * @method Message\MessageContext getMessageContext()
41
 * @method Redirect\RedirectContext getRedirectContext()
42
 * @method TqContext getTqContext()
43
 * @method DrupalContexts\MinkContext getMinkContext()
44
 * @method DrupalContexts\DrupalContext getDrupalContext()
45
 * @method Random getRandom()
46
 */
47
class RawTqContext extends RawPageContext implements TqContextInterface
48
{
49
    use JavaScript;
50
    use Debugger;
51
    use Tags;
52
53
    /**
54
     * Parameters of TqExtension.
55
     *
56
     * @var array
57
     */
58
    private $parameters = [];
59
    /**
60
     * @var string
61
     */
62
    protected static $pageUrl = '';
63
64
    /**
65
     * @param string $method
66
     * @param array $arguments
67
     *
68
     * @throws \Exception
69
     * @throws ContextNotFoundException
70
     *   When context class cannot be loaded.
71
     *
72
     * @return SnippetAcceptingContext
73
     */
74
    public function __call($method, array $arguments)
75
    {
76
        $environment = $this->getEnvironment();
77
        // @example
78
        // The "getFormContext" method is not declared and his name will be split by capital
79
        // letters, creating an array with three items: "get", "Form" and "Context".
80
        list(, $base, $context) = preg_split('/(?=[A-Z])/', $method);
81
82
        $namespace = [$this->getTqParameter('namespace'), 'Context'];
83
84
        // Provide a possibility to use "getTqContext()" method. Otherwise class will be looked
85
        // up into "\Drupal\TqExtension\Context\Tq\TqContext" namespace which does not exists.
86
        if ('Tq' !== $base) {
87
            $namespace[] = $base;
88
        }
89
90
        foreach ([$namespace, ['Drupal', 'DrupalExtension', 'Context']] as $class) {
91
            $class[] = "$base$context";
92
            $class = implode('\\', $class);
93
94
            if ($environment->hasContextClass($class)) {
95
                return $environment->getContext($class);
96
            }
97
        }
98
99
        throw new \Exception(sprintf('Method "%s" does not exist', $method));
100
    }
101
102
    /**
103
     * Get selector by name.
104
     *
105
     * @param string $name
106
     *   Selector name from the configuration file.
107
     *
108
     * @return string
109
     *   CSS selector.
110
     *
111
     * @throws \Exception
112
     *   If selector does not exits.
113
     */
114
    public function getDrupalSelector($name)
115
    {
116
        $selectors = $this->getDrupalParameter('selectors');
117
118
        if (!isset($selectors[$name])) {
119
            throw new \Exception(sprintf('No such selector configured: %s', $name));
120
        }
121
122
        return $selectors[$name];
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function getDrupalText($name)
129
    {
130
        // Make text selectors translatable.
131
        return DrupalKernelPlaceholder::t(parent::getDrupalText($name));
132
    }
133
134
    /**
135
     * @param string $site
136
     *   Drupal site folder.
137
     *
138
     * @return string
139
     *   URL to files directory.
140
     */
141
    public function getFilesUrl($site = 'default')
142
    {
143
        return $this->locatePath("sites/$site/files");
144
    }
145
146
    /**
147
     * @return Environment|InitializedContextEnvironment
148
     */
149
    public function getEnvironment()
150
    {
151
        return $this->getDrupal()->getEnvironment();
152
    }
153
154
    /**
155
     * @return SessionDriverInterface|Selenium2Driver|GoutteDriver
156
     */
157
    public function getSessionDriver()
158
    {
159
        return $this->getSession()->getDriver();
160
    }
161
162
    /**
163
     * @return Session
164
     */
165
    public function getWebDriverSession()
166
    {
167
        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...
168
    }
169
170
    /**
171
     * @todo Remove this when DrupalExtension will be used Mink >=1.6 and use $this->getSession->getWindowNames();
172
     *
173
     * @return string[]
174
     */
175
    public function getWindowNames()
176
    {
177
        return $this->getWebDriverSession()->window_handles();
178
    }
179
180
    /**
181
     * @param NodeElement $element
182
     * @param string $script
183
     *
184
     * @example
185
     * $this->executeJsOnElement($this->element('*', 'Meta tags'), 'return jQuery({{ELEMENT}}).text();');
186
     * $this->executeJsOnElement($this->element('*', '#menu'), '{{ELEMENT}}.focus();');
187
     *
188
     * @throws \Exception
189
     *
190
     * @return mixed
191
     */
192
    public function executeJsOnElement(NodeElement $element, $script)
193
    {
194
        $session = $this->getWebDriverSession();
195
        // We need to trigger something with "withSyn" method, because, otherwise an element won't be found.
196
        $element->focus();
197
198
        self::debug(['%s'], [$script]);
199
200
        return $session->execute([
201
            'script' => str_replace('{{ELEMENT}}', 'arguments[0]', $script),
202
            'args' => [['ELEMENT' => $session->element('xpath', $element->getXpath())->getID()]],
203
        ]);
204
    }
205
206
    /**
207
     * @param string $javascript
208
     *   JS code for execution.
209
     * @param array $args
210
     *   Placeholder declarations.
211
     *
212
     * @return mixed
213
     */
214
    public function executeJs($javascript, array $args = [])
215
    {
216
        $javascript = DrupalKernelPlaceholder::formatString($javascript, $args);
217
218
        self::debug([$javascript]);
219
220
        return $this->getSession()->evaluateScript($javascript);
221
    }
222
223
    /**
224
     * Check JS events in step definition.
225
     *
226
     * @param StepScope $event
227
     *
228
     * @return int
229
     */
230
    public static function isStepImpliesJsEvent(StepScope $event)
231
    {
232
        return self::hasTag('javascript') && preg_match('/(follow|press|click|submit)/i', $event->getStep()->getText());
233
    }
234
235
    /**
236
     * @return DrupalDriverInterface|DrushDriver
237
     */
238
    public function getDrushDriver()
239
    {
240
        return $this->getDriver('drush');
241
    }
242
243
    /**
244
     * Wait for all AJAX requests and jQuery animations.
245
     */
246
    public function waitAjaxAndAnimations()
247
    {
248
        $script = [];
249
        $script[] = '!window.__ajaxRequestsInProcess';
250
        $script[] = "(window.jQuery ? !jQuery(':animated').length && !jQuery.active : true)";
251
252
        $this->getSession()->wait(2000, implode(' && ', $script));
253
    }
254
255
    /**
256
     * {@inheritdoc}
257
     */
258
    public function setTqParameters(array $parameters)
259
    {
260
        if (empty($this->parameters)) {
261
            $this->parameters = $parameters;
262
        }
263
    }
264
265
    /**
266
     * {@inheritdoc}
267
     */
268
    public function getTqParameter($name)
269
    {
270
        return isset($this->parameters[$name]) ? $this->parameters[$name] : false;
271
    }
272
273
    /**
274
     * {@inheritdoc}
275
     */
276
    public function locatePath($path = '')
277
    {
278
        return (string) new Url($this->getMinkParameter('base_url'), $path);
279
    }
280
281
    /**
282
     * @return string
283
     *   Absolute URL.
284
     */
285
    public function getCurrentUrl()
286
    {
287
        return $this->locatePath($this->getSession()->getCurrentUrl());
288
    }
289
}
290