Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like CmsUiContext 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 CmsUiContext, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class CmsUiContext implements Context |
||
18 | { |
||
19 | use MainContextAwareTrait; |
||
20 | use StepHelper; |
||
21 | |||
22 | /** |
||
23 | * Get Mink session from MinkContext |
||
24 | * |
||
25 | * @return Session |
||
26 | */ |
||
27 | public function getSession($name = null) |
||
31 | |||
32 | /** |
||
33 | * Wait until CMS loading overlay isn't present. |
||
34 | * This is an addition to the "ajax steps" logic in |
||
35 | * SilverStripe\BehatExtension\Context\BasicContext |
||
36 | * which also waits for any ajax requests to finish before continuing. |
||
37 | * |
||
38 | * The check also applies in when not in the CMS, which is a structural issue: |
||
39 | * Every step could cause the CMS to be loaded, and we don't know if we're in the |
||
40 | * CMS UI until we run a check. |
||
41 | * |
||
42 | * Excluding scenarios with @modal tag is required, |
||
43 | * because modal dialogs stop any JS interaction |
||
44 | * |
||
45 | * @AfterStep ~@modal |
||
46 | */ |
||
47 | public function handleCmsLoadingAfterStep(StepEvent $event) |
||
55 | |||
56 | /** |
||
57 | * @Then /^I should see the CMS$/ |
||
58 | */ |
||
59 | public function iShouldSeeTheCms() |
||
65 | |||
66 | /** |
||
67 | * @Then /^I should see a "([^"]*)" notice$/ |
||
68 | */ |
||
69 | public function iShouldSeeANotice($notice) |
||
73 | |||
74 | /** |
||
75 | * @Then /^I should see a "([^"]*)" message$/ |
||
76 | */ |
||
77 | public function iShouldSeeAMessage($message) |
||
81 | |||
82 | View Code Duplication | protected function getCmsTabsElement() |
|
83 | { |
||
84 | $this->getSession()->wait( |
||
85 | 5000, |
||
86 | "window.jQuery && window.jQuery('.cms-content-header-tabs').size() > 0" |
||
87 | ); |
||
88 | |||
89 | $page = $this->getSession()->getPage(); |
||
90 | $cms_content_header_tabs = $page->find('css', '.cms-content-header-tabs'); |
||
91 | assertNotNull($cms_content_header_tabs, 'CMS tabs not found'); |
||
92 | |||
93 | return $cms_content_header_tabs; |
||
94 | } |
||
95 | |||
96 | View Code Duplication | protected function getCmsContentToolbarElement() |
|
97 | { |
||
98 | $this->getSession()->wait( |
||
99 | 5000, |
||
100 | "window.jQuery && window.jQuery('.cms-content-toolbar').size() > 0 " |
||
101 | . "&& window.jQuery('.cms-content-toolbar').children().size() > 0" |
||
102 | ); |
||
103 | |||
104 | $page = $this->getSession()->getPage(); |
||
105 | $cms_content_toolbar_element = $page->find('css', '.cms-content-toolbar'); |
||
106 | assertNotNull($cms_content_toolbar_element, 'CMS content toolbar not found'); |
||
107 | |||
108 | return $cms_content_toolbar_element; |
||
109 | } |
||
110 | |||
111 | View Code Duplication | protected function getCmsTreeElement() |
|
112 | { |
||
113 | $this->getSession()->wait( |
||
114 | 5000, |
||
115 | "window.jQuery && window.jQuery('.cms-tree').size() > 0" |
||
116 | ); |
||
117 | |||
118 | $page = $this->getSession()->getPage(); |
||
119 | $cms_tree_element = $page->find('css', '.cms-tree'); |
||
120 | assertNotNull($cms_tree_element, 'CMS tree not found'); |
||
121 | |||
122 | return $cms_tree_element; |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * @Given /^I should see a "([^"]*)" button in CMS Content Toolbar$/ |
||
127 | */ |
||
128 | public function iShouldSeeAButtonInCmsContentToolbar($text) |
||
135 | |||
136 | /** |
||
137 | * @When /^I should see "([^"]*)" in the tree$/ |
||
138 | */ |
||
139 | View Code Duplication | public function stepIShouldSeeInCmsTree($text) |
|
146 | |||
147 | /** |
||
148 | * @When /^I should not see "([^"]*)" in the tree$/ |
||
149 | */ |
||
150 | View Code Duplication | public function stepIShouldNotSeeInCmsTree($text) |
|
157 | |||
158 | /** |
||
159 | * Applies a specific action to an element |
||
160 | * |
||
161 | * @param NodeElement $element Element to act on |
||
162 | * @param string $action Action, which may be one of 'hover', 'double click', 'right click', or 'left click' |
||
163 | * The default 'click' behaves the same as left click |
||
164 | */ |
||
165 | protected function interactWithElement($element, $action = 'click') |
||
184 | |||
185 | /** |
||
186 | * @When /^I (?P<method>(?:(?:double |right |left |)click)|hover) on "(?P<link>[^"]*)" in the context menu/ |
||
187 | */ |
||
188 | public function stepIClickOnElementInTheContextMenu($method, $link) |
||
210 | |||
211 | /** |
||
212 | * @When /^I (?P<method>(?:(?:double |right |left |)click)|hover) on "(?P<text>[^"]*)" in the tree$/ |
||
213 | */ |
||
214 | public function stepIClickOnElementInTheTree($method, $text) |
||
221 | |||
222 | /** |
||
223 | * @When /^I expand the "([^"]*)" CMS Panel$/ |
||
224 | */ |
||
225 | public function iExpandTheCmsPanel() |
||
237 | |||
238 | /** |
||
239 | * @When /^I (expand|collapse) the content filters$/ |
||
240 | */ |
||
241 | public function iExpandTheContentFilters($action) |
||
272 | |||
273 | /** |
||
274 | * @When /^I (expand|collapse) "([^"]*)" in the tree$/ |
||
275 | */ |
||
276 | public function iExpandInTheTree($action, $nodeText) |
||
300 | |||
301 | /** |
||
302 | * @When /^I should (not |)see a "([^"]*)" CMS tab$/ |
||
303 | */ |
||
304 | public function iShouldSeeACmsTab($negate, $tab) |
||
305 | { |
||
306 | $this->getSession()->wait( |
||
307 | 5000, |
||
308 | "window.jQuery && window.jQuery('.ui-tabs-nav').size() > 0" |
||
309 | ); |
||
310 | |||
311 | $page = $this->getSession()->getPage(); |
||
312 | $tabsets = $page->findAll('css', '.ui-tabs-nav'); |
||
313 | assertNotNull($tabsets, 'CMS tabs not found'); |
||
314 | |||
315 | $tab_element = null; |
||
316 | View Code Duplication | foreach ($tabsets as $tabset) { |
|
317 | $tab_element = $tabset->find('named', array('link_or_button', "'$tab'")); |
||
318 | if ($tab_element) { |
||
319 | break; |
||
320 | } |
||
321 | } |
||
322 | if ($negate) { |
||
323 | assertNull($tab_element, sprintf('%s tab found', $tab)); |
||
324 | } else { |
||
325 | assertNotNull($tab_element, sprintf('%s tab not found', $tab)); |
||
326 | } |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * @When /^I click the "([^"]*)" CMS tab$/ |
||
331 | */ |
||
332 | public function iClickTheCmsTab($tab) |
||
333 | { |
||
334 | $this->getSession()->wait( |
||
335 | 5000, |
||
336 | "window.jQuery && window.jQuery('.ui-tabs-nav').size() > 0" |
||
337 | ); |
||
338 | |||
339 | $page = $this->getSession()->getPage(); |
||
340 | $tabsets = $page->findAll('css', '.ui-tabs-nav'); |
||
341 | assertNotNull($tabsets, 'CMS tabs not found'); |
||
342 | |||
343 | $tab_element = null; |
||
344 | View Code Duplication | foreach ($tabsets as $tabset) { |
|
345 | if ($tab_element) { |
||
346 | continue; |
||
347 | } |
||
348 | $tab_element = $tabset->find('named', array('link_or_button', "'$tab'")); |
||
349 | } |
||
350 | assertNotNull($tab_element, sprintf('%s tab not found', $tab)); |
||
351 | |||
352 | $tab_element->click(); |
||
353 | } |
||
354 | |||
355 | /** |
||
356 | * @Then /^I can see the preview panel$/ |
||
357 | */ |
||
358 | public function iCanSeeThePreviewPanel() |
||
362 | |||
363 | /** |
||
364 | * @Given /^the preview contains "([^"]*)"$/ |
||
365 | */ |
||
366 | View Code Duplication | public function thePreviewContains($content) |
|
367 | { |
||
368 | $driver = $this->getSession()->getDriver(); |
||
369 | // TODO Remove once we have native support in Mink and php-webdriver, |
||
370 | // see https://groups.google.com/forum/#!topic/behat/QNhOuGHKEWI |
||
371 | $origWindowName = $driver->getWebDriverSession()->window_handle(); |
||
372 | |||
373 | $driver->switchToIFrame('cms-preview-iframe'); |
||
374 | $this->getMainContext()->assertPageContainsText($content); |
||
375 | $driver->switchToWindow($origWindowName); |
||
376 | } |
||
377 | |||
378 | /** |
||
379 | * @Given /^I set the CMS mode to "([^"]*)"$/ |
||
380 | */ |
||
381 | public function iSetTheCmsToMode($mode) |
||
386 | |||
387 | /** |
||
388 | * @Given /^I wait for the preview to load$/ |
||
389 | */ |
||
390 | View Code Duplication | public function iWaitForThePreviewToLoad() |
|
391 | { |
||
392 | $driver = $this->getSession()->getDriver(); |
||
393 | // TODO Remove once we have native support in Mink and php-webdriver, |
||
394 | // see https://groups.google.com/forum/#!topic/behat/QNhOuGHKEWI |
||
395 | $origWindowName = $driver->getWebDriverSession()->window_handle(); |
||
396 | |||
397 | $driver->switchToIFrame('cms-preview-iframe'); |
||
398 | $this->getSession()->wait( |
||
399 | 5000, |
||
400 | "window.jQuery && !window.jQuery('iframe[name=cms-preview-iframe]').hasClass('loading')" |
||
401 | ); |
||
402 | $driver->switchToWindow($origWindowName); |
||
403 | } |
||
404 | |||
405 | /** |
||
406 | * @Given /^I switch the preview to "([^"]*)"$/ |
||
407 | */ |
||
408 | public function iSwitchThePreviewToMode($mode) |
||
423 | |||
424 | /** |
||
425 | * @Given /^the preview does not contain "([^"]*)"$/ |
||
426 | */ |
||
427 | View Code Duplication | public function thePreviewDoesNotContain($content) |
|
428 | { |
||
429 | $driver = $this->getSession()->getDriver(); |
||
430 | // TODO Remove once we have native support in Mink and php-webdriver, |
||
431 | // see https://groups.google.com/forum/#!topic/behat/QNhOuGHKEWI |
||
432 | $origWindowName = $driver->getWebDriverSession()->window_handle(); |
||
433 | |||
434 | $driver->switchToIFrame('cms-preview-iframe'); |
||
435 | $this->getMainContext()->assertPageNotContainsText($content); |
||
436 | $driver->switchToWindow($origWindowName); |
||
437 | } |
||
438 | |||
439 | /** |
||
440 | * When I follow "my link" in preview |
||
441 | * |
||
442 | * @When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)" in preview$/ |
||
443 | */ |
||
444 | View Code Duplication | public function clickLinkInPreview($link) |
|
445 | { |
||
446 | $driver = $this->getSession()->getDriver(); |
||
447 | // TODO Remove once we have native support in Mink and php-webdriver, |
||
448 | // see https://groups.google.com/forum/#!topic/behat/QNhOuGHKEWI |
||
449 | $origWindowName = $driver->getWebDriverSession()->window_handle(); |
||
450 | $driver->switchToIFrame('cms-preview-iframe'); |
||
451 | |||
452 | $link = $this->fixStepArgument($link); |
||
453 | $this->getSession()->getPage()->clickLink($link); |
||
454 | |||
455 | $driver->switchToWindow($origWindowName); |
||
456 | } |
||
457 | |||
458 | /** |
||
459 | * When I press "submit" in preview |
||
460 | * |
||
461 | * @When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)" in preview$/ |
||
462 | */ |
||
463 | View Code Duplication | public function pressButtonInPreview($button) |
|
464 | { |
||
465 | $driver = $this->getSession()->getDriver(); |
||
466 | // TODO Remove once we have native support in Mink and php-webdriver, |
||
467 | // see https://groups.google.com/forum/#!topic/behat/QNhOuGHKEWI |
||
468 | $origWindowName = $driver->getWebDriverSession()->window_handle(); |
||
469 | $driver->switchToIFrame('cms-preview-iframe'); |
||
470 | |||
471 | $button = $this->fixStepArgument($button); |
||
472 | $this->getSession()->getPage()->pressButton($button); |
||
473 | |||
474 | $driver->switchToWindow($origWindowName); |
||
475 | } |
||
476 | |||
477 | /** |
||
478 | * Workaround for chosen.js dropdowns or tree dropdowns which hide the original dropdown field. |
||
479 | * |
||
480 | * @When /^(?:|I )fill in the "(?P<field>(?:[^"]|\\")*)" dropdown with "(?P<value>(?:[^"]|\\")*)"$/ |
||
481 | * @When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for the "(?P<field>(?:[^"]|\\")*)" dropdown$/ |
||
482 | */ |
||
483 | public function theIFillInTheDropdownWith($field, $value) |
||
582 | |||
583 | /** |
||
584 | * Returns fixed step argument (with \\" replaced back to "). |
||
585 | * |
||
586 | * @param string $argument |
||
587 | * |
||
588 | * @return string |
||
589 | */ |
||
590 | protected function fixStepArgument($argument) |
||
594 | |||
595 | /** |
||
596 | * Returns the closest parent element having a specific class attribute. |
||
597 | * |
||
598 | * @param NodeElement $el |
||
599 | * @param String $class |
||
600 | * @return Element|null |
||
601 | */ |
||
602 | protected function findParentByClass(NodeElement $el, $class) |
||
614 | } |
||
615 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.