Passed
Pull Request — 4 (#10028)
by Steve
06:56
created

CmsFormsContext   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 464
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 173
dl 0
loc 464
rs 8.48
c 0
b 0
f 0
wmc 49

How to fix   Complexity   

Complex Class

Complex classes like CmsFormsContext often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CmsFormsContext, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\Framework\Tests\Behaviour;
4
5
use BadMethodCallException;
6
use Behat\Behat\Context\Context;
7
use Behat\Mink\Exception\ElementHtmlException;
8
use Behat\Gherkin\Node\TableNode;
9
use Behat\Mink\Session;
10
use PHPUnit\Framework\Assert;
11
use SilverStripe\BehatExtension\Context\MainContextAwareTrait;
12
use SilverStripe\BehatExtension\Utility\StepHelper;
13
use Symfony\Component\DomCrawler\Crawler;
14
use Behat\Mink\Element\NodeElement;
15
use SilverStripe\SiteConfig\SiteConfig;
16
17
/**
18
 * CmsFormsContext
19
 *
20
 * Context used to define steps related to forms inside CMS.
21
 */
22
class CmsFormsContext implements Context
23
{
24
    use MainContextAwareTrait;
25
    use StepHelper;
26
27
    /**
28
     * Get Mink session from MinkContext
29
     *
30
     * @param string $name
31
     * @return Session
32
     */
33
    public function getSession($name = null)
34
    {
35
        return $this->getMainContext()->getSession($name);
36
    }
37
38
    /**
39
     * Returns fixed step argument (with \\" replaced back to ").
40
     * Copied from {@see MinkContext}
41
     *
42
     * @param string $argument
43
     * @return string
44
     */
45
    protected function fixStepArgument($argument)
46
    {
47
        return str_replace('\\"', '"', $argument);
48
    }
49
50
    /**
51
     * @Then /^I should( not? |\s*)see an edit page form$/
52
     */
53
    public function stepIShouldSeeAnEditPageForm($negative)
54
    {
55
        $page = $this->getSession()->getPage();
56
57
        $form = $page->find('css', '#Form_EditForm');
58
        if (trim($negative)) {
59
            Assert::assertNull($form, 'I should not see an edit page form');
60
        } else {
61
            Assert::assertNotNull($form, 'I should see an edit page form');
62
        }
63
    }
64
65
    /**
66
     * @When /^I fill in the "(?P<field>(?:[^"]|\\")*)" HTML field with "(?P<value>(?:[^"]|\\")*)"$/
67
     * @When /^I fill in "(?P<value>(?:[^"]|\\")*)" for the "(?P<field>(?:[^"]|\\")*)" HTML field$/
68
     */
69
    public function stepIFillInTheHtmlFieldWith($field, $value)
70
    {
71
        $inputField = $this->getHtmlField($field);
72
        $value = $this->fixStepArgument($value);
73
74
        $this->getSession()->evaluateScript(sprintf(
75
            "jQuery('#%s').entwine('ss').getEditor().setContent('%s')",
76
            $inputField->getAttribute('id'),
77
            addcslashes($value, "'")
78
        ));
79
        $this->getSession()->evaluateScript(sprintf(
80
            "jQuery('#%s').entwine('ss').getEditor().save()",
81
            $inputField->getAttribute('id')
82
        ));
83
    }
84
85
    /**
86
     * @When /^I append "(?P<value>(?:[^"]|\\")*)" to the "(?P<field>(?:[^"]|\\")*)" HTML field$/
87
     */
88
    public function stepIAppendTotheHtmlField($field, $value)
89
    {
90
        $inputField = $this->getHtmlField($field);
91
        $value = $this->fixStepArgument($value);
92
93
        $this->getSession()->evaluateScript(sprintf(
94
            "jQuery('#%s').entwine('ss').getEditor().insertContent('%s')",
95
            $inputField->getAttribute('id'),
96
            addcslashes($value, "'")
97
        ));
98
    }
99
100
    /**
101
     * @Then /^the "(?P<locator>(?:[^"]|\\")*)" HTML field should(?P<negative> not? |\s*)contain "(?P<html>.*)"$/
102
     */
103
    public function theHtmlFieldShouldContain($locator, $negative, $html)
104
    {
105
        $element = $this->getHtmlField($locator);
106
        $actual = $element->getValue();
107
        $regex = '/' . preg_quote($html, '/') . '/ui';
108
        $failed = false;
109
110
        if (trim($negative)) {
111
            if (preg_match($regex, $actual)) {
112
                $failed = true;
113
            }
114
        } else {
115
            if (!preg_match($regex, $actual)) {
116
                $failed = true;
117
            }
118
        }
119
120
        if ($failed) {
121
            $message = sprintf(
122
                'The string "%s" should%sbe found in the HTML of the element matching name "%s". Actual content: "%s"',
123
                $html,
124
                $negative,
125
                $locator,
126
                $actual
127
            );
128
            throw new ElementHtmlException($message, $this->getSession(), $element);
129
        }
130
    }
131
132
	// @codingStandardsIgnoreStart
133
	/**
134
	 * Checks formatting in the HTML field, by analyzing the HTML node surrounding
135
	 * the text for certain properties.
136
	 *
137
	 * Example: Given "my text" in the "Content" HTML field should be right aligned
138
	 * Example: Given "my text" in the "Content" HTML field should not be bold
139
	 *
140
	 * @todo Use an actual DOM parser for more accurate assertions
141
	 *
142
	 * @Given /^"(?P<text>([^"]*))" in the "(?P<field>(?:[^"]|\\")*)" HTML field should(?P<negate>(?: not)?) be (?P<formatting>(.*))$/
143
	 */
144
	public function stepContentInHtmlFieldShouldHaveFormatting($text, $field, $negate, $formatting) {
145
		$inputField = $this->getHtmlField($field);
146
147
		$crawler = new Crawler($inputField->getValue());
148
		$matchedNode = null;
149
		foreach($crawler->filterXPath('//*') as $node) {
150
			if(
151
				$node->firstChild
152
				&& $node->firstChild->nodeType == XML_TEXT_NODE
153
				&& stripos($node->firstChild->nodeValue, $text) !== FALSE
154
			) {
155
				$matchedNode = $node;
156
			}
157
		}
158
		assertNotNull($matchedNode);
159
160
		$assertFn = $negate ? 'assertNotEquals' : 'assertEquals';
161
		if($formatting == 'bold') {
162
			call_user_func($assertFn, 'strong', $matchedNode->nodeName);
163
		} else if($formatting == 'left aligned') {
164
			if($matchedNode->getAttribute('class')) {
165
				call_user_func($assertFn, 'text-left', $matchedNode->getAttribute('class'));
166
			}
167
		} else if($formatting == 'right aligned') {
168
			call_user_func($assertFn, 'text-right', $matchedNode->getAttribute('class'));
169
		}
170
	}
171
	// @codingStandardsIgnoreEnd
172
173
    /**
174
     * Selects the first textual match in the HTML editor. Does not support
175
     * selection across DOM node boundaries.
176
     *
177
     * @When /^I select "(?P<text>([^"]*))" in the "(?P<field>(?:[^"]|\\")*)" HTML field$/
178
     */
179
    public function stepIHighlightTextInHtmlField($text, $field)
180
    {
181
        $inputField = $this->getHtmlField($field);
182
        $inputFieldId = $inputField->getAttribute('id');
183
        $text = addcslashes($text, "'");
184
185
        $js = <<<JS
186
// TODO <IE9 support
187
// TODO Allow text matches across nodes
188
var editor = jQuery('#$inputFieldId').entwine('ss').getEditor(),
189
	doc = editor.getInstance().getDoc(),
190
	sel = editor.getInstance().selection,
191
	rng = document.createRange(),
192
	matched = false;
193
194
editor.getInstance().focus();
195
jQuery(doc).find('body *').each(function() {
196
	if(!matched) {
197
		for(var i=0;i<this.childNodes.length;i++) {
198
			if(!matched && this.childNodes[i].nodeValue && this.childNodes[i].nodeValue.match('$text')) {
199
				rng.setStart(this.childNodes[i], this.childNodes[i].nodeValue.indexOf('$text'));
200
				rng.setEnd(this.childNodes[i], this.childNodes[i].nodeValue.indexOf('$text') + '$text'.length);
201
				sel.setRng(rng);
202
				editor.getInstance().nodeChanged();
203
				matched = true;
204
				break;
205
			}
206
		}
207
	}
208
});
209
JS;
210
211
        $this->getSession()->executeScript($js);
212
    }
213
214
    /**
215
     * @Given /^I should( not? |\s*)see a "([^"]*)" field$/
216
     */
217
    public function iShouldSeeAField($negative, $text)
218
    {
219
        $page = $this->getSession()->getPage();
220
        $els = $page->findAll('named', ['field', "'$text'"]);
221
        $matchedEl = null;
222
        /** @var NodeElement $el */
223
        foreach ($els as $el) {
224
            if ($el->isVisible()) {
225
                $matchedEl = $el;
226
            }
227
        }
228
229
        if (trim($negative)) {
230
            Assert::assertNull($matchedEl);
231
        } else {
232
            Assert::assertNotNull($matchedEl);
233
        }
234
    }
235
236
    /**
237
     * Click on the element with the provided CSS Selector
238
     *
239
     * @When /^I press the "([^"]*)" HTML field button$/
240
     */
241
    public function iClickOnTheHtmlFieldButton($button)
242
    {
243
        $xpath = "//*[@aria-label='" . $button . "']";
244
        $session = $this->getSession();
245
        $element = $session->getPage()->find('xpath', $xpath);
246
        if (null === $element) {
247
            // If it can't find the exact name, find one that starts with the phrase
248
            // Helpful for "Insert link" which has a conditional label for keyboard shortcut
249
            $xpath = "//*[starts-with(@aria-label, '" . $button . "')]";
250
            $element = $session->getPage()->find('xpath', $xpath);
251
252
            if (null === $element) {
253
                throw new \InvalidArgumentException(sprintf('Could not find element with xpath %s', $xpath));
254
            };
255
        }
256
257
        $element->click();
258
    }
259
260
    /*
261
     * @example Given the CMS settings has the following data
262
     *  | Title | My site title |
263
     *  | Theme | My site theme |
264
     * @Given /^the CMS settings have the following data$/
265
     */
266
    public function theCmsSettingsHasData(TableNode $fieldsTable)
267
    {
268
        $fields = $fieldsTable->getRowsHash();
269
        $siteConfig = SiteConfig::get()->first();
270
        foreach ($fields as $field => $value) {
271
            $siteConfig->$field = $value;
272
        }
273
        $siteConfig->write();
274
        $siteConfig->flushCache();
275
    }
276
277
    /**
278
     * Select a value in the tree dropdown field
279
     *
280
     * NOTE: This is react specific, may need to move to its own react section later
281
     *
282
     * @When /^I select "([^"]*)" in the "([^"]*)" tree dropdown$/
283
     */
284
    public function iSelectValueInTreeDropdown($text, $selector)
285
    {
286
        $page = $this->getSession()->getPage();
287
        /** @var NodeElement $parentElement */
288
        $parentElement = null;
289
        $this->retryThrowable(function () use (&$parentElement, &$page, $selector) {
290
            $parentElement = $page->find('css', $selector);
291
            Assert::assertNotNull($parentElement, sprintf('"%s" element not found', $selector));
292
            $page = $this->getSession()->getPage();
293
        });
294
295
        $this->retryThrowable(function () use ($parentElement, $selector) {
296
            $dropdown = $parentElement->find('css', '.Select-arrow');
297
            Assert::assertNotNull($dropdown, sprintf('Unable to find the dropdown in "%s"', $selector));
298
            $dropdown->click();
299
        });
300
301
        $this->retryThrowable(function () use ($text, $parentElement, $selector) {
302
            $element = $parentElement->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text));
303
            Assert::assertNotNull($element, sprintf('"%s" not found in "%s"', $text, $selector));
304
            $element->click();
305
        });
306
    }
307
308
    /**
309
     * Locate an HTML editor field
310
     *
311
     * @param string $locator Raw html field identifier as passed from
312
     * @return NodeElement
313
     */
314
    protected function getHtmlField($locator)
315
    {
316
        $locator = $this->fixStepArgument($locator);
317
        $page = $this->getSession()->getPage();
318
        
319
        // Searching by name is usually good...
320
        $element = $page->find('css', 'textarea.htmleditor[name=\'' . $locator . '\']');
321
        
322
        if ($element === null) {
323
            $element = $this->findInputByLabelContent($locator);
324
        }
325
        
326
        Assert::assertNotNull($element, sprintf('HTML field "%s" not found', $locator));
327
        return $element;
328
    }
329
330
    protected function findInputByLabelContent($locator)
331
    {
332
        $page = $this->getSession()->getPage();
333
        $label = $page->findAll('xpath', sprintf('//label[contains(text(), \'%s\')]', $locator));
334
335
        if (empty($label)) {
336
            return null;
337
        }
338
339
        Assert::assertCount(1, $label, sprintf(
340
            'Found more than one element containing the phrase "%s".',
341
            $locator
342
        ));
343
344
        $label = array_shift($label);
345
346
        $fieldId = $label->getAttribute('for');
347
        return $page->find('css', '#' . $fieldId);
348
    }
349
350
    /**
351
     * @Given /^the "([^"]*)" field ((?:does not have)|(?:has)) property "([^"]*)"$/
352
     */
353
    public function Assert::assertTheFieldHasProperty($name, $cond, $property)
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM, expecting '(' on line 353 at column 26
Loading history...
354
    {
355
        $name = $this->fixStepArgument($name);
356
        $property = $this->fixStepArgument($property);
357
358
        $context = $this->getMainContext();
359
        $fieldObj = $context->assertSession()->fieldExists($name);
360
361
        // Check property
362
        $hasProperty = $fieldObj->hasAttribute($property);
363
        switch ($cond) {
364
            case 'has':
365
                assert($hasProperty, "Field $name does not have property $property");
366
                break;
367
            case 'does not have':
368
                assert(!$hasProperty, "Field $name should not have property $property");
369
                break;
370
            default:
371
                throw new BadMethodCallException("Invalid condition");
372
        }
373
    }
374
375
    /**
376
     * @When /^I switch to the "([^"]*)" iframe$/
377
     * @param string $id iframe id property
378
     */
379
    public function stepSwitchToTheFrame($id)
380
    {
381
        $this->getMainContext()->getSession()->getDriver()->switchToIFrame($id);
382
    }
383
384
    /**
385
     * @When /^I am not in an iframe$/
386
     */
387
    public function stepSwitchToParentFrame()
388
    {
389
        $this->getMainContext()->getSession()->getDriver()->switchToIFrame(null);
390
    }
391
392
    /**
393
     * @When /^my session expires$/
394
     */
395
    public function stepMySessionExpires()
396
    {
397
        // Destroy cookie to detach session
398
        $this->getMainContext()->getSession()->setCookie('PHPSESSID', null);
399
    }
400
401
    /**
402
     * @When /^I should see the "([^"]*)" button in the "([^"]*)" gridfield for the "([^"]*)" row$/
403
     * @param string $buttonLabel
404
     * @param string $gridFieldName
405
     * @param string $rowName
406
     */
407
    public function Assert::assertIShouldSeeTheGridFieldButtonForRow($buttonLabel, $gridFieldName, $rowName)
408
    {
409
        $button = $this->getGridFieldButton($gridFieldName, $rowName, $buttonLabel);
410
        Assert::assertNotNull($button, sprintf('Button "%s" not found', $buttonLabel));
411
    }
412
413
    /**
414
     * @When /^I should not see the "([^"]*)" button in the "([^"]*)" gridfield for the "([^"]*)" row$/
415
     * @param string $buttonLabel
416
     * @param string $gridFieldName
417
     * @param string $rowName
418
     */
419
    public function Assert::assertIShouldNotSeeTheGridFieldButtonForRow($buttonLabel, $gridFieldName, $rowName)
420
    {
421
        $button = $this->getGridFieldButton($gridFieldName, $rowName, $buttonLabel);
422
        Assert::assertNull($button, sprintf('Button "%s" found', $buttonLabel));
423
    }
424
425
    /**
426
     * @When /^I click the "([^"]*)" button in the "([^"]*)" gridfield for the "([^"]*)" row$/
427
     * @param string $buttonLabel
428
     * @param string $gridFieldName
429
     * @param string $rowName
430
     */
431
    public function stepIClickTheGridFieldButtonForRow($buttonLabel, $gridFieldName, $rowName)
432
    {
433
        $button = $this->getGridFieldButton($gridFieldName, $rowName, $buttonLabel);
434
        Assert::assertNotNull($button, sprintf('Button "%s" not found', $buttonLabel));
435
436
        $button->click();
437
    }
438
439
    /**
440
     * Finds a button in the gridfield row
441
     *
442
     * @param $gridFieldName
443
     * @param $rowName
444
     * @param $buttonLabel
445
     * @return $button
446
     */
447
    protected function getGridFieldButton($gridFieldName, $rowName, $buttonLabel)
448
    {
449
        $page = $this->getSession()->getPage();
450
        $gridField = $page->find('xpath', sprintf('//*[@data-name="%s"]', $gridFieldName));
451
        Assert::assertNotNull($gridField, sprintf('Gridfield "%s" not found', $gridFieldName));
452
453
        $name = $gridField->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $rowName));
454
        if (!$name) {
455
            return null;
456
        }
457
458
        if ($dropdownButton = $name->getParent()->find('css', '.action-menu__toggle')) {
459
            $dropdownButton->click();
460
        }
461
462
        $button = $name->getParent()->find('named', ['link_or_button', $buttonLabel]);
463
464
        return $button;
465
    }
466
467
    /**
468
     * @When /^I click the "([^"]*)" option in the "([^"]*)" listbox$/
469
     * @param $optionLabel
470
     * @param $fieldName
471
     */
472
    public function stepIClickTheListBoxOption($optionLabel, $fieldName)
473
    {
474
        $page = $this->getSession()->getPage();
475
        $listBox = $page->find('xpath', sprintf('//*[@name="%s[]"]', $fieldName));
476
        Assert::assertNotNull($listBox, sprintf('The listbox %s is not found', $fieldName));
477
478
        $option = $listBox->getParent()
479
            ->find('css', '.chosen-choices')
480
            ->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $optionLabel));
481
        Assert::assertNotNull($option, sprintf('Option %s is not found', $optionLabel));
482
483
        $button = $option->getParent()->find('css', 'a');
484
485
        $button->click();
486
    }
487
}
488