Completed
Push — master ( 622a07...7c2344 )
by Daniel
108:51 queued 72:30
created

CmsFormsContext::iShouldSeeAButton()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 6
nop 2
dl 0
loc 14
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Framework\Test\Behaviour;
4
5
use Behat\Behat\Context\ClosuredContextInterface;
6
use Behat\Behat\Context\TranslatedContextInterface;
7
use Behat\Behat\Context\BehatContext;
8
use Behat\Behat\Context\Step;
9
use Behat\Behat\Exception\PendingException;
10
use Behat\Mink\Exception\ElementHtmlException;
11
use Behat\Gherkin\Node\PyStringNode;
12
use Behat\Gherkin\Node\TableNode;
13
use Behat\MinkExtension\Context\MinkContext as MinkContext;
14
use Symfony\Component\DomCrawler\Crawler;
15
use Behat\Mink\Element\NodeElement;
16
use SilverStripe\SiteConfig\SiteConfig;
17
18
19
/**
20
 * CmsFormsContext
21
 *
22
 * Context used to define steps related to forms inside CMS.
23
 */
24
class CmsFormsContext extends BehatContext {
25
	protected $context;
26
27
	/**
28
	 * Initializes context.
29
	 * Every scenario gets it's own context object.
30
	 *
31
	 * @param   array   $parameters     context parameters (set them up through behat.yml)
32
	 */
33
	public function __construct(array $parameters) {
34
		// Initialize your context here
35
		$this->context = $parameters;
36
	}
37
38
	/**
39
	 * Get Mink session from MinkContext
40
	 */
41
	public function getSession($name = null) {
42
		return $this->getMainContext()->getSession($name);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\ExtendedContextInterface as the method getSession() does only exist in the following implementations of said interface: Behat\MinkExtension\Context\MinkContext, Behat\MinkExtension\Context\RawMinkContext, SilverStripe\BehatExtension\Context\BasicContext, SilverStripe\BehatExtension\Context\EmailContext, SilverStripe\BehatExtension\Context\FixtureContext, SilverStripe\BehatExtension\Context\LoginContext, SilverStripe\BehatExtens...ext\SilverStripeContext, SilverStripe\Framework\T...haviour\CmsFormsContext, SilverStripe\Framework\Test\Behaviour\CmsUiContext, SilverStripe\Framework\T...ehaviour\FeatureContext.

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...
43
	}
44
45
	/**
46
	 * Returns fixed step argument (with \\" replaced back to ").
47
	 * Copied from {@see MinkContext}
48
	 *
49
	 * @param string $argument
50
	 * @return string
51
	 */
52
	protected function fixStepArgument($argument) {
53
		return str_replace('\\"', '"', $argument);
54
	}
55
56
	/**
57
	 * @Then /^I should( not? |\s*)see an edit page form$/
58
	 */
59
	public function stepIShouldSeeAnEditPageForm($negative) {
60
		$page = $this->getSession()->getPage();
61
62
		$form = $page->find('css', '#Form_EditForm');
63
		if(trim($negative)) {
64
			assertNull($form, 'I should not see an edit page form');
65
		} else {
66
			assertNotNull($form, 'I should see an edit page form');
67
		}
68
	}
69
70
	/**
71
	 * @When /^I fill in the "(?P<field>(?:[^"]|\\")*)" HTML field with "(?P<value>(?:[^"]|\\")*)"$/
72
	 * @When /^I fill in "(?P<value>(?:[^"]|\\")*)" for the "(?P<field>(?:[^"]|\\")*)" HTML field$/
73
	 */
74
	public function stepIFillInTheHtmlFieldWith($field, $value) {
75
		$inputField = $this->getHtmlField($field);
76
		$value = $this->fixStepArgument($value);
77
78
		$this->getSession()->evaluateScript(sprintf(
79
			"jQuery('#%s').entwine('ss').getEditor().setContent('%s')",
80
			$inputField->getAttribute('id'),
81
			addcslashes($value, "'")
82
		));
83
	}
84
85
	/**
86
	 * @When /^I append "(?P<value>(?:[^"]|\\")*)" to the "(?P<field>(?:[^"]|\\")*)" HTML field$/
87
	 */
88
	public function stepIAppendTotheHtmlField($field, $value) {
89
		$inputField = $this->getHtmlField($field);
90
		$value = $this->fixStepArgument($value);
91
92
		$this->getSession()->evaluateScript(sprintf(
93
			"jQuery('#%s').entwine('ss').getEditor().insertContent('%s')",
94
			$inputField->getAttribute('id'),
95
			addcslashes($value, "'")
96
		));
97
	}
98
99
	/**
100
	 * @Then /^the "(?P<locator>(?:[^"]|\\")*)" HTML field should(?P<negative> not? |\s*)contain "(?P<html>.*)"$/
101
	 */
102
	public function theHtmlFieldShouldContain($locator, $negative, $html) {
103
		$element = $this->getHtmlField($locator);
104
		$actual = $element->getValue();
105
		$regex = '/'.preg_quote($html, '/').'/ui';
106
		$failed = false;
107
108
		if(trim($negative)) {
109
			if (preg_match($regex, $actual)) {
110
				$failed = true;
111
			}
112
		} else {
113
			if (!preg_match($regex, $actual)) {
114
				$failed = true;
115
			}
116
		}
117
118
		if($failed) {
119
			$message = sprintf(
120
				'The string "%s" should%sbe found in the HTML of the element matching name "%s". Actual content: "%s"',
121
				$html,
122
				$negative,
123
				$locator,
124
				$actual
125
			);
126
			throw new ElementHtmlException($message, $this->getSession(), $element);
127
		}
128
	}
129
130
	// @codingStandardsIgnoreStart
131
	/**
132
	 * Checks formatting in the HTML field, by analyzing the HTML node surrounding
133
	 * the text for certain properties.
134
	 *
135
	 * Example: Given "my text" in the "Content" HTML field should be right aligned
136
	 * Example: Given "my text" in the "Content" HTML field should not be bold
137
	 *
138
	 * @todo Use an actual DOM parser for more accurate assertions
139
	 *
140
	 * @Given /^"(?P<text>([^"]*))" in the "(?P<field>(?:[^"]|\\")*)" HTML field should(?P<negate>(?: not)?) be (?P<formatting>(.*))$/
141
	 */
142
	public function stepContentInHtmlFieldShouldHaveFormatting($text, $field, $negate, $formatting) {
143
		$inputField = $this->getHtmlField($field);
144
145
		$crawler = new Crawler($inputField->getValue());
146
		$matchedNode = null;
147
		foreach($crawler->filterXPath('//*') as $node) {
148
			if(
149
				$node->firstChild
150
				&& $node->firstChild->nodeType == XML_TEXT_NODE
151
				&& stripos($node->firstChild->nodeValue, $text) !== FALSE
152
			) {
153
				$matchedNode = $node;
154
			}
155
		}
156
		assertNotNull($matchedNode);
157
158
		$assertFn = $negate ? 'assertNotEquals' : 'assertEquals';
159
		if($formatting == 'bold') {
160
			call_user_func($assertFn, 'strong', $matchedNode->nodeName);
161
		} else if($formatting == 'left aligned') {
162
			if($matchedNode->getAttribute('style')) {
163
				call_user_func($assertFn, 'text-align: left;', $matchedNode->getAttribute('style'));
164
			}
165
		} else if($formatting == 'right aligned') {
166
			call_user_func($assertFn, 'text-align: right;', $matchedNode->getAttribute('style'));
167
		}
168
	}
169
	// @codingStandardsIgnoreEnd
170
171
	/**
172
	 * Selects the first textual match in the HTML editor. Does not support
173
	 * selection across DOM node boundaries.
174
	 *
175
	 * @When /^I select "(?P<text>([^"]*))" in the "(?P<field>(?:[^"]|\\")*)" HTML field$/
176
	 */
177
	public function stepIHighlightTextInHtmlField($text, $field) {
178
		$inputField = $this->getHtmlField($field);
179
		$inputFieldId = $inputField->getAttribute('id');
180
		$text = addcslashes($text, "'");
181
182
		$js = <<<JS
183
// TODO <IE9 support
184
// TODO Allow text matches across nodes
185
var editor = jQuery('#$inputFieldId').entwine('ss').getEditor(),
186
	doc = editor.getInstance().getDoc(),
187
	sel = editor.getInstance().selection,
188
	rng = document.createRange(),
189
	matched = false;
190
191
jQuery(doc).find('body *').each(function() {
192
	if(!matched) {
193
		for(var i=0;i<this.childNodes.length;i++) {
194
			if(!matched && this.childNodes[i].nodeValue && this.childNodes[i].nodeValue.match('$text')) {
195
				rng.setStart(this.childNodes[i], this.childNodes[i].nodeValue.indexOf('$text'));
196
				rng.setEnd(this.childNodes[i], this.childNodes[i].nodeValue.indexOf('$text') + '$text'.length);
197
				sel.setRng(rng);
198
				editor.getInstance().nodeChanged();
199
				matched = true;
200
				break;
201
			}
202
		}
203
	}
204
});
205
JS;
206
207
		$this->getSession()->executeScript($js);
208
	}
209
210
	/**
211
	 * Example: I should see a "Submit" button
212
	 * Example: I should not see a "Delete" button
213
	 *
214
	 * @Given /^I should( not? |\s*)see a "([^"]*)" button$/
215
	 */
216
	public function iShouldSeeAButton($negative, $text) {
217
		$page = $this->getSession()->getPage();
218
		$els = $page->findAll('named', array('link_or_button', "'$text'"));
219
		$matchedEl = null;
220
		foreach($els as $el) {
221
			if($el->isVisible()) $matchedEl = $el;
222
		}
223
224
		if(trim($negative)) {
225
			assertNull($matchedEl, sprintf('%s button found', $text));
226
		} else {
227
			assertNotNull($matchedEl, sprintf('%s button not found', $text));
228
		}
229
	}
230
231
	/**
232
	 * @Given /^I should( not? |\s*)see a "([^"]*)" field$/
233
	 */
234
	public function iShouldSeeAField($negative, $text) {
235
		$page = $this->getSession()->getPage();
236
		$els = $page->findAll('named', array('field', "'$text'"));
237
		$matchedEl = null;
238
		foreach($els as $el) {
239
			if($el->isVisible()) $matchedEl = $el;
240
		}
241
242
		if(trim($negative)) {
243
			assertNull($matchedEl);
244
		} else {
245
			assertNotNull($matchedEl);
246
		}
247
	}
248
249
	/**
250
     * Click on the element with the provided CSS Selector
251
     *
252
     * @When /^I press the "([^"]*)" HTML field button$/
253
     */
254
    public function iClickOnTheHtmlFieldButton($button)
255
    {
256
		$xpath = "//*[@aria-label='".$button."']";
257
        $session = $this->getSession();
258
        $element = $session->getPage()->find('xpath', $xpath);
259
        if (null === $element) {
260
            throw new \InvalidArgumentException(sprintf('Could not find element with xpath %s', $xpath));
261
        }
262
263
        $element->click();
264
    }
265
266
	/*
267
	 * @example Given the CMS settings has the following data
268
	 *	| Title | My site title |
269
	 *	| Theme | My site theme |
270
	 * @Given /^the CMS settings have the following data$/
271
	 */
272
	public function theCmsSettingsHasData(TableNode $fieldsTable) {
273
		$fields = $fieldsTable->getRowsHash();
274
		$siteConfig = SiteConfig::get()->first();
275
		foreach($fields as $field => $value) {
276
			$siteConfig->$field = $value;
277
		}
278
		$siteConfig->write();
279
		$siteConfig->flushCache();
280
	}
281
282
	/**
283
	 * Locate an HTML editor field
284
	 *
285
	 * @param string $locator Raw html field identifier as passed from
286
	 * @return NodeElement
287
	 */
288
	protected function getHtmlField($locator)
289
	{
290
		$locator = $this->fixStepArgument($locator);
291
		$page = $this->getSession()->getPage();
292
		$element = $page->find('css', 'textarea.htmleditor[name=\'' . $locator . '\']');
293
		assertNotNull($element, sprintf('HTML field "%s" not found', $locator));
294
		return $element;
295
	}
296
297
}
298