FileListContext.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. <?php
  2. /**
  3. *
  4. * @copyright Copyright (c) 2018, Daniel Calviño Sánchez (danxuliu@gmail.com)
  5. *
  6. * @license GNU AGPL version 3 or any later version
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License as
  10. * published by the Free Software Foundation, either version 3 of the
  11. * License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. */
  22. use Behat\Behat\Context\Context;
  23. class FileListContext implements Context, ActorAwareInterface {
  24. /**
  25. * @var Actor
  26. */
  27. private $actor;
  28. /**
  29. * @var array
  30. */
  31. private $fileListAncestorsByActor;
  32. /**
  33. * @var Locator
  34. */
  35. private $fileListAncestor;
  36. /**
  37. * @BeforeScenario
  38. */
  39. public function initializeFileListAncestors() {
  40. $this->fileListAncestorsByActor = array();
  41. $this->fileListAncestor = null;
  42. }
  43. /**
  44. * @param Actor $actor
  45. */
  46. public function setCurrentActor(Actor $actor) {
  47. $this->actor = $actor;
  48. if (array_key_exists($actor->getName(), $this->fileListAncestorsByActor)) {
  49. $this->fileListAncestor = $this->fileListAncestorsByActor[$actor->getName()];
  50. } else {
  51. $this->fileListAncestor = null;
  52. }
  53. }
  54. /**
  55. * Sets the file list ancestor to be used in the steps performed by the
  56. * given actor from that point on (until changed again).
  57. *
  58. * This is meant to be called from other contexts, for example, when the
  59. * Files app or the public page for a shared folder are opened.
  60. *
  61. * The FileListAncestorSetter trait can be used to reduce the boilerplate
  62. * needed to set the file list ancestor from other contexts.
  63. *
  64. * @param null|Locator $fileListAncestor the file list ancestor
  65. * @param Actor $actor the actor
  66. */
  67. public function setFileListAncestorForActor($fileListAncestor, Actor $actor) {
  68. $this->fileListAncestorsByActor[$actor->getName()] = $fileListAncestor;
  69. }
  70. /**
  71. * @return Locator
  72. */
  73. public static function mainWorkingIcon($fileListAncestor) {
  74. return Locator::forThe()->css(".mask.icon-loading")->
  75. descendantOf($fileListAncestor)->
  76. describedAs("Main working icon in file list");
  77. }
  78. /**
  79. * @return Locator
  80. */
  81. public static function breadcrumbs($fileListAncestor) {
  82. return Locator::forThe()->css("#controls .breadcrumb")->
  83. descendantOf($fileListAncestor)->
  84. describedAs("Breadcrumbs in file list");
  85. }
  86. /**
  87. * @return Locator
  88. */
  89. public static function createMenuButton($fileListAncestor) {
  90. return Locator::forThe()->css("#controls .button.new")->
  91. descendantOf($fileListAncestor)->
  92. describedAs("Create menu button in file list");
  93. }
  94. /**
  95. * @return Locator
  96. */
  97. private static function createMenuItemFor($fileListAncestor, $newType) {
  98. return Locator::forThe()->xpath("//div[contains(concat(' ', normalize-space(@class), ' '), ' newFileMenu ')]//span[normalize-space() = '$newType']/ancestor::li")->
  99. descendantOf($fileListAncestor)->
  100. describedAs("Create $newType menu item in file list");
  101. }
  102. /**
  103. * @return Locator
  104. */
  105. public static function createNewFolderMenuItem($fileListAncestor) {
  106. return self::createMenuItemFor($fileListAncestor, "New folder");
  107. }
  108. /**
  109. * @return Locator
  110. */
  111. public static function createNewFolderMenuItemNameInput($fileListAncestor) {
  112. return Locator::forThe()->css(".filenameform input")->
  113. descendantOf(self::createNewFolderMenuItem($fileListAncestor))->
  114. describedAs("Name input in create new folder menu item in file list");
  115. }
  116. /**
  117. * @return Locator
  118. */
  119. public static function fileListHeader($fileListAncestor) {
  120. return Locator::forThe()->css("thead")->
  121. descendantOf($fileListAncestor)->
  122. describedAs("Header in file list");
  123. }
  124. /**
  125. * @return Locator
  126. */
  127. public static function selectedFilesActionsMenuButton($fileListAncestor) {
  128. return Locator::forThe()->css(".actions-selected")->
  129. descendantOf(self::fileListHeader($fileListAncestor))->
  130. describedAs("Selected files actions menu button in file list");
  131. }
  132. /**
  133. * @return Locator
  134. */
  135. public static function selectedFilesActionsMenu() {
  136. return Locator::forThe()->css(".filesSelectMenu")->
  137. describedAs("Selected files actions menu in file list");
  138. }
  139. /**
  140. * @return Locator
  141. */
  142. private static function selectedFilesActionsMenuItemFor($itemText) {
  143. return Locator::forThe()->xpath("//a[normalize-space() = '$itemText']")->
  144. descendantOf(self::selectedFilesActionsMenu())->
  145. describedAs($itemText . " item in selected files actions menu in file list");
  146. }
  147. /**
  148. * @return Locator
  149. */
  150. public static function moveOrCopySelectedFilesMenuItem() {
  151. return self::selectedFilesActionsMenuItemFor("Move or copy");
  152. }
  153. /**
  154. * @return Locator
  155. */
  156. public static function rowForFile($fileListAncestor, $fileName) {
  157. return Locator::forThe()->xpath("//*[@id = 'fileList']//span[contains(concat(' ', normalize-space(@class), ' '), ' nametext ') and normalize-space() = '$fileName']/ancestor::tr")->
  158. descendantOf($fileListAncestor)->
  159. describedAs("Row for file $fileName in file list");
  160. }
  161. /**
  162. * @return Locator
  163. */
  164. public static function rowForFilePreceding($fileListAncestor, $fileName1, $fileName2) {
  165. return Locator::forThe()->xpath("//preceding-sibling::tr//span[contains(concat(' ', normalize-space(@class), ' '), ' nametext ') and normalize-space() = '$fileName1']/ancestor::tr")->
  166. descendantOf(self::rowForFile($fileListAncestor, $fileName2))->
  167. describedAs("Row for file $fileName1 preceding $fileName2 in file list");
  168. }
  169. /**
  170. * @return Locator
  171. */
  172. public static function selectionCheckboxForFile($fileListAncestor, $fileName) {
  173. // Note that the element that the user interacts with is the label, not
  174. // the checbox itself.
  175. return Locator::forThe()->css(".selection label")->
  176. descendantOf(self::rowForFile($fileListAncestor, $fileName))->
  177. describedAs("Selection checkbox for file $fileName in file list");
  178. }
  179. /**
  180. * @return Locator
  181. */
  182. public static function selectionCheckboxInputForFile($fileListAncestor, $fileName) {
  183. return Locator::forThe()->css(".selection input[type=checkbox]")->
  184. descendantOf(self::rowForFile($fileListAncestor, $fileName))->
  185. describedAs("Selection checkbox input for file $fileName in file list");
  186. }
  187. /**
  188. * @return Locator
  189. */
  190. public static function favoriteMarkForFile($fileListAncestor, $fileName) {
  191. return Locator::forThe()->css(".favorite-mark")->
  192. descendantOf(self::rowForFile($fileListAncestor, $fileName))->
  193. describedAs("Favorite mark for file $fileName in file list");
  194. }
  195. /**
  196. * @return Locator
  197. */
  198. public static function notFavoritedStateIconForFile($fileListAncestor, $fileName) {
  199. return Locator::forThe()->css(".icon-star")->
  200. descendantOf(self::favoriteMarkForFile($fileListAncestor, $fileName))->
  201. describedAs("Not favorited state icon for file $fileName in file list");
  202. }
  203. /**
  204. * @return Locator
  205. */
  206. public static function favoritedStateIconForFile($fileListAncestor, $fileName) {
  207. return Locator::forThe()->css(".icon-starred")->
  208. descendantOf(self::favoriteMarkForFile($fileListAncestor, $fileName))->
  209. describedAs("Favorited state icon for file $fileName in file list");
  210. }
  211. /**
  212. * @return Locator
  213. */
  214. public static function mainLinkForFile($fileListAncestor, $fileName) {
  215. return Locator::forThe()->css(".name")->
  216. descendantOf(self::rowForFile($fileListAncestor, $fileName))->
  217. describedAs("Main link for file $fileName in file list");
  218. }
  219. /**
  220. * @return Locator
  221. */
  222. public static function renameInputForFile($fileListAncestor, $fileName) {
  223. return Locator::forThe()->css("input.filename")->
  224. descendantOf(self::rowForFile($fileListAncestor, $fileName))->
  225. describedAs("Rename input for file $fileName in file list");
  226. }
  227. /**
  228. * @return Locator
  229. */
  230. public static function commentActionForFile($fileListAncestor, $fileName) {
  231. return Locator::forThe()->css(".action-comment")->
  232. descendantOf(self::rowForFile($fileListAncestor, $fileName))->
  233. describedAs("Comment action for file $fileName in file list");
  234. }
  235. /**
  236. * @return Locator
  237. */
  238. public static function shareActionForFile($fileListAncestor, $fileName) {
  239. return Locator::forThe()->css(".action-share")->
  240. descendantOf(self::rowForFile($fileListAncestor, $fileName))->
  241. describedAs("Share action for file $fileName in file list");
  242. }
  243. /**
  244. * @return Locator
  245. */
  246. public static function fileActionsMenuButtonForFile($fileListAncestor, $fileName) {
  247. return Locator::forThe()->css(".action-menu")->
  248. descendantOf(self::rowForFile($fileListAncestor, $fileName))->
  249. describedAs("File actions menu button for file $fileName in file list");
  250. }
  251. /**
  252. * @return Locator
  253. */
  254. public static function fileActionsMenu() {
  255. return Locator::forThe()->css(".fileActionsMenu")->
  256. describedAs("File actions menu in file list");
  257. }
  258. /**
  259. * @return Locator
  260. */
  261. private static function fileActionsMenuItemFor($itemText) {
  262. return Locator::forThe()->xpath("//a[normalize-space() = '$itemText']")->
  263. descendantOf(self::fileActionsMenu())->
  264. describedAs($itemText . " item in file actions menu in file list");
  265. }
  266. /**
  267. * @return Locator
  268. */
  269. public static function addToFavoritesMenuItem() {
  270. return self::fileActionsMenuItemFor("Add to favorites");
  271. }
  272. /**
  273. * @return Locator
  274. */
  275. public static function removeFromFavoritesMenuItem() {
  276. return self::fileActionsMenuItemFor("Remove from favorites");
  277. }
  278. /**
  279. * @return Locator
  280. */
  281. public static function detailsMenuItem() {
  282. return self::fileActionsMenuItemFor("Details");
  283. }
  284. /**
  285. * @return Locator
  286. */
  287. public static function renameMenuItem() {
  288. return self::fileActionsMenuItemFor("Rename");
  289. }
  290. /**
  291. * @return Locator
  292. */
  293. public static function moveOrCopyMenuItem() {
  294. return self::fileActionsMenuItemFor("Move or copy");
  295. }
  296. /**
  297. * @return Locator
  298. */
  299. public static function viewFileInFolderMenuItem() {
  300. return self::fileActionsMenuItemFor("View in folder");
  301. }
  302. /**
  303. * @return Locator
  304. */
  305. public static function deleteMenuItem() {
  306. return self::fileActionsMenuItemFor("Delete");
  307. }
  308. /**
  309. * @Given I create a new folder named :folderName
  310. */
  311. public function iCreateANewFolderNamed($folderName) {
  312. $this->actor->find(self::createMenuButton($this->fileListAncestor), 10)->click();
  313. $this->actor->find(self::createNewFolderMenuItem($this->fileListAncestor), 2)->click();
  314. $this->actor->find(self::createNewFolderMenuItemNameInput($this->fileListAncestor), 2)->setValue($folderName . "\r");
  315. }
  316. /**
  317. * @Given I enter in the folder named :folderName
  318. */
  319. public function iEnterInTheFolderNamed($folderName) {
  320. $this->actor->find(self::mainLinkForFile($this->fileListAncestor, $folderName), 10)->click();
  321. }
  322. /**
  323. * @Given I select :fileName
  324. */
  325. public function iSelect($fileName) {
  326. $this->iSeeThatIsNotSelected($fileName);
  327. $this->actor->find(self::selectionCheckboxForFile($this->fileListAncestor, $fileName), 10)->click();
  328. }
  329. /**
  330. * @Given I start the move or copy operation for the selected files
  331. */
  332. public function iStartTheMoveOrCopyOperationForTheSelectedFiles() {
  333. $this->actor->find(self::selectedFilesActionsMenuButton($this->fileListAncestor), 10)->click();
  334. $this->actor->find(self::moveOrCopySelectedFilesMenuItem(), 2)->click();
  335. }
  336. /**
  337. * @Given I open the details view for :fileName
  338. */
  339. public function iOpenTheDetailsViewFor($fileName) {
  340. $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $fileName), 10)->click();
  341. $this->actor->find(self::detailsMenuItem(), 2)->click();
  342. }
  343. /**
  344. * @Given I rename :fileName1 to :fileName2
  345. */
  346. public function iRenameTo($fileName1, $fileName2) {
  347. $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $fileName1), 10)->click();
  348. $this->actor->find(self::renameMenuItem(), 2)->click();
  349. // For reference, due to a bug in the Firefox driver of Selenium and/or
  350. // maybe in Firefox itself, as a range is selected in the rename input
  351. // (the name of the file, without its extension) when the value is set
  352. // the window must be in the foreground. Otherwise, if the window is in
  353. // the background, instead of setting the value in the whole field it
  354. // would be set only in the selected range.
  355. // This should not be a problem, though, as the default behaviour is to
  356. // bring the browser window to the foreground when switching to a
  357. // different actor.
  358. $this->actor->find(self::renameInputForFile($this->fileListAncestor, $fileName1), 10)->setValue($fileName2 . "\r");
  359. }
  360. /**
  361. * @Given I start the move or copy operation for :fileName
  362. */
  363. public function iStartTheMoveOrCopyOperationFor($fileName) {
  364. $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $fileName), 10)->click();
  365. $this->actor->find(self::moveOrCopyMenuItem(), 2)->click();
  366. }
  367. /**
  368. * @Given I mark :fileName as favorite
  369. */
  370. public function iMarkAsFavorite($fileName) {
  371. $this->iSeeThatIsNotMarkedAsFavorite($fileName);
  372. $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $fileName), 10)->click();
  373. $this->actor->find(self::addToFavoritesMenuItem(), 2)->click();
  374. }
  375. /**
  376. * @Given I unmark :fileName as favorite
  377. */
  378. public function iUnmarkAsFavorite($fileName) {
  379. $this->iSeeThatIsMarkedAsFavorite($fileName);
  380. $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $fileName), 10)->click();
  381. $this->actor->find(self::removeFromFavoritesMenuItem(), 2)->click();
  382. }
  383. /**
  384. * @When I view :fileName in folder
  385. */
  386. public function iViewInFolder($fileName) {
  387. $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $fileName), 10)->click();
  388. $this->actor->find(self::viewFileInFolderMenuItem(), 2)->click();
  389. }
  390. /**
  391. * @When I delete :fileName
  392. */
  393. public function iDelete($fileName) {
  394. $this->actor->find(self::fileActionsMenuButtonForFile($this->fileListAncestor, $fileName), 10)->click();
  395. $this->actor->find(self::deleteMenuItem(), 2)->click();
  396. }
  397. /**
  398. * @When I open the unread comments for :fileName
  399. */
  400. public function iOpenTheUnreadCommentsFor($fileName) {
  401. $this->actor->find(self::commentActionForFile($this->fileListAncestor, $fileName), 10)->click();
  402. }
  403. /**
  404. * @Then I see that the file list is eventually loaded
  405. */
  406. public function iSeeThatTheFileListIsEventuallyLoaded() {
  407. if (!WaitFor::elementToBeEventuallyNotShown(
  408. $this->actor,
  409. self::mainWorkingIcon($this->fileListAncestor),
  410. $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
  411. PHPUnit_Framework_Assert::fail("The main working icon for the file list is still shown after $timeout seconds");
  412. }
  413. }
  414. /**
  415. * @Then I see that the file list is currently in :path
  416. */
  417. public function iSeeThatTheFileListIsCurrentlyIn($path) {
  418. // The text of the breadcrumbs is the text of all the crumbs separated
  419. // by white spaces.
  420. PHPUnit_Framework_Assert::assertEquals(
  421. str_replace('/', ' ', $path), $this->actor->find(self::breadcrumbs($this->fileListAncestor), 10)->getText());
  422. }
  423. /**
  424. * @Then I see that it is not possible to create new files
  425. */
  426. public function iSeeThatItIsNotPossibleToCreateNewFiles() {
  427. // Once a file list is loaded the "Create" menu button is always in the
  428. // DOM, so it is checked if it is visible or not.
  429. PHPUnit_Framework_Assert::assertFalse($this->actor->find(self::createMenuButton($this->fileListAncestor))->isVisible());
  430. }
  431. /**
  432. * @Then I see that the file list contains a file named :fileName
  433. */
  434. public function iSeeThatTheFileListContainsAFileNamed($fileName) {
  435. PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::rowForFile($this->fileListAncestor, $fileName), 10));
  436. }
  437. /**
  438. * @Then I see that the file list does not contain a file named :fileName
  439. */
  440. public function iSeeThatTheFileListDoesNotContainAFileNamed($fileName) {
  441. if (!WaitFor::elementToBeEventuallyNotShown(
  442. $this->actor,
  443. self::rowForFile($this->fileListAncestor, $fileName),
  444. $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
  445. PHPUnit_Framework_Assert::fail("The file list still contains a file named $fileName after $timeout seconds");
  446. }
  447. }
  448. /**
  449. * @Then I see that :fileName1 precedes :fileName2 in the file list
  450. */
  451. public function iSeeThatPrecedesInTheFileList($fileName1, $fileName2) {
  452. PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::rowForFilePreceding($this->fileListAncestor, $fileName1, $fileName2), 10));
  453. }
  454. /**
  455. * @Then I see that :fileName is not selected
  456. */
  457. public function iSeeThatIsNotSelected($fileName) {
  458. PHPUnit_Framework_Assert::assertFalse($this->actor->find(self::selectionCheckboxInputForFile($this->fileListAncestor, $fileName), 10)->isChecked());
  459. }
  460. /**
  461. * @Then I see that :fileName is marked as favorite
  462. */
  463. public function iSeeThatIsMarkedAsFavorite($fileName) {
  464. PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::favoritedStateIconForFile($this->fileListAncestor, $fileName), 10));
  465. }
  466. /**
  467. * @Then I see that :fileName is not marked as favorite
  468. */
  469. public function iSeeThatIsNotMarkedAsFavorite($fileName) {
  470. PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::notFavoritedStateIconForFile($this->fileListAncestor, $fileName), 10));
  471. }
  472. /**
  473. * @Then I see that :fileName has unread comments
  474. */
  475. public function iSeeThatHasUnreadComments($fileName) {
  476. PHPUnit_Framework_Assert::assertTrue($this->actor->find(self::commentActionForFile($this->fileListAncestor, $fileName), 10)->isVisible());
  477. }
  478. }