FilesAppContext.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. <?php
  2. /**
  3. *
  4. * @copyright Copyright (c) 2017, 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 FilesAppContext implements Context, ActorAwareInterface {
  24. use ActorAware;
  25. use FileListAncestorSetter;
  26. /**
  27. * @return array
  28. */
  29. public static function sections() {
  30. return [ "All files" => "files",
  31. "Recent" => "recent",
  32. "Favorites" => "favorites",
  33. "Shared with you" => "sharingin",
  34. "Shared with others" => "sharingout",
  35. "Shared by link" => "sharinglinks",
  36. "Tags" => "systemtagsfilter",
  37. "Deleted files" => "trashbin" ];
  38. }
  39. /**
  40. * @return Locator
  41. */
  42. public static function mainViewForSection($section) {
  43. $sectionId = self::sections()[$section];
  44. return Locator::forThe()->id("app-content-$sectionId")->
  45. describedAs("Main view for section $section in Files app");
  46. }
  47. /**
  48. * @return Locator
  49. */
  50. public static function currentSectionMainView() {
  51. return Locator::forThe()->xpath("//*[starts-with(@id, 'app-content-') and not(contains(concat(' ', normalize-space(@class), ' '), ' hidden '))]")->
  52. describedAs("Current section main view in Files app");
  53. }
  54. /**
  55. * @return Locator
  56. */
  57. public static function detailsViewForSection($section) {
  58. return Locator::forThe()->xpath("/preceding-sibling::*[position() = 1 and @id = 'app-sidebar']")->
  59. descendantOf(self::mainViewForSection($section))->
  60. describedAs("Details view for section $section in Files app");
  61. }
  62. /**
  63. * @return Locator
  64. */
  65. public static function currentSectionDetailsView() {
  66. return Locator::forThe()->xpath("/preceding-sibling::*[position() = 1 and @id = 'app-sidebar']")->
  67. descendantOf(self::currentSectionMainView())->
  68. describedAs("Current section details view in Files app");
  69. }
  70. /**
  71. * @return Locator
  72. */
  73. public static function closeDetailsViewButton() {
  74. return Locator::forThe()->css(".icon-close")->
  75. descendantOf(self::currentSectionDetailsView())->
  76. describedAs("Close current section details view in Files app");
  77. }
  78. /**
  79. * @return Locator
  80. */
  81. public static function fileNameInCurrentSectionDetailsView() {
  82. return Locator::forThe()->css(".fileName")->
  83. descendantOf(self::currentSectionDetailsView())->
  84. describedAs("File name in current section details view in Files app");
  85. }
  86. /**
  87. * @return Locator
  88. */
  89. public static function fileDetailsInCurrentSectionDetailsViewWithText($fileDetailsText) {
  90. return Locator::forThe()->xpath("//span[normalize-space() = '$fileDetailsText']")->
  91. descendantOf(self::fileDetailsInCurrentSectionDetailsView())->
  92. describedAs("File details with text \"$fileDetailsText\" in current section details view in Files app");
  93. }
  94. /**
  95. * @return Locator
  96. */
  97. private static function fileDetailsInCurrentSectionDetailsView() {
  98. return Locator::forThe()->css(".file-details")->
  99. descendantOf(self::currentSectionDetailsView())->
  100. describedAs("File details in current section details view in Files app");
  101. }
  102. /**
  103. * @return Locator
  104. */
  105. public static function inputFieldForTagsInCurrentSectionDetailsView() {
  106. return Locator::forThe()->css(".systemTagsInfoView")->
  107. descendantOf(self::currentSectionDetailsView())->
  108. describedAs("Input field for tags in current section details view in Files app");
  109. }
  110. /**
  111. * @return Locator
  112. */
  113. public static function itemInInputFieldForTagsInCurrentSectionDetailsViewForTag($tag) {
  114. return Locator::forThe()->xpath("//span[normalize-space() = '$tag']")->
  115. descendantOf(self::inputFieldForTagsInCurrentSectionDetailsView())->
  116. describedAs("Item in input field for tags in current section details view for tag $tag in Files app");
  117. }
  118. /**
  119. * @return Locator
  120. */
  121. public static function itemInDropdownForTag($tag) {
  122. return Locator::forThe()->xpath("//*[contains(concat(' ', normalize-space(@class), ' '), ' select2-result-label ')]//span[normalize-space() = '$tag']/ancestor::li")->
  123. descendantOf(self::select2Dropdown())->
  124. describedAs("Item in dropdown for tag $tag in Files app");
  125. }
  126. /**
  127. * @return Locator
  128. */
  129. public static function checkmarkInItemInDropdownForTag($tag) {
  130. return Locator::forThe()->css(".checkmark")->
  131. descendantOf(self::itemInDropdownForTag($tag))->
  132. describedAs("Checkmark in item in dropdown for tag $tag in Files app");
  133. }
  134. /**
  135. * @return Locator
  136. */
  137. private static function select2Dropdown() {
  138. return Locator::forThe()->css("#select2-drop")->
  139. describedAs("Select2 dropdown in Files app");
  140. }
  141. /**
  142. * @return Locator
  143. */
  144. public static function tabHeaderInCurrentSectionDetailsViewNamed($tabHeaderName) {
  145. return Locator::forThe()->xpath("//li[normalize-space() = '$tabHeaderName']")->
  146. descendantOf(self::tabHeadersInCurrentSectionDetailsView())->
  147. describedAs("Tab header named $tabHeaderName in current section details view in Files app");
  148. }
  149. /**
  150. * @return Locator
  151. */
  152. private static function tabHeadersInCurrentSectionDetailsView() {
  153. return Locator::forThe()->css(".tabHeaders")->
  154. descendantOf(self::currentSectionDetailsView())->
  155. describedAs("Tab headers in current section details view in Files app");
  156. }
  157. /**
  158. * @return Locator
  159. */
  160. public static function tabInCurrentSectionDetailsViewNamed($tabName) {
  161. return Locator::forThe()->xpath("//div[@id=//*[contains(concat(' ', normalize-space(@class), ' '), ' tabHeader ') and normalize-space() = '$tabName']/@data-tabid]")->
  162. descendantOf(self::currentSectionDetailsView())->
  163. describedAs("Tab named $tabName in current section details view in Files app");
  164. }
  165. /**
  166. * @return Locator
  167. */
  168. public static function loadingIconForTabInCurrentSectionDetailsViewNamed($tabName) {
  169. return Locator::forThe()->css(".loading")->
  170. descendantOf(self::tabInCurrentSectionDetailsViewNamed($tabName))->
  171. describedAs("Loading icon for tab named $tabName in current section details view in Files app");
  172. }
  173. /**
  174. * @return Locator
  175. */
  176. public static function shareLinkCheckbox() {
  177. // forThe()->checkbox("Share link") can not be used here; that would
  178. // return the checkbox itself, but the element that the user interacts
  179. // with is the label.
  180. return Locator::forThe()->xpath("//label[normalize-space() = 'Share link']")->
  181. descendantOf(self::currentSectionDetailsView())->
  182. describedAs("Share link checkbox in the details view in Files app");
  183. }
  184. /**
  185. * @return Locator
  186. */
  187. public static function shareLinkField() {
  188. return Locator::forThe()->css(".linkText")->descendantOf(self::currentSectionDetailsView())->
  189. describedAs("Share link field in the details view in Files app");
  190. }
  191. /**
  192. * @return Locator
  193. */
  194. public static function allowUploadAndEditingRadioButton() {
  195. // forThe()->radio("Allow upload and editing") can not be used here;
  196. // that would return the radio button itself, but the element that the
  197. // user interacts with is the label.
  198. return Locator::forThe()->xpath("//label[normalize-space() = 'Allow upload and editing']")->
  199. descendantOf(self::currentSectionDetailsView())->
  200. describedAs("Allow upload and editing radio button in the details view in Files app");
  201. }
  202. /**
  203. * @return Locator
  204. */
  205. public static function passwordProtectCheckbox() {
  206. // forThe()->checkbox("Password protect") can not be used here; that
  207. // would return the checkbox itself, but the element that the user
  208. // interacts with is the label.
  209. return Locator::forThe()->xpath("//label[normalize-space() = 'Password protect']")->
  210. descendantOf(self::currentSectionDetailsView())->
  211. describedAs("Password protect checkbox in the details view in Files app");
  212. }
  213. /**
  214. * @return Locator
  215. */
  216. public static function passwordProtectField() {
  217. return Locator::forThe()->css(".linkPassText")->descendantOf(self::currentSectionDetailsView())->
  218. describedAs("Password protect field in the details view in Files app");
  219. }
  220. /**
  221. * @return Locator
  222. */
  223. public static function passwordProtectWorkingIcon() {
  224. return Locator::forThe()->css(".linkPass .icon-loading-small")->descendantOf(self::currentSectionDetailsView())->
  225. describedAs("Password protect working icon in the details view in Files app");
  226. }
  227. /**
  228. * @Given I close the details view
  229. */
  230. public function iCloseTheDetailsView() {
  231. $this->actor->find(self::closeDetailsViewButton(), 10)->click();
  232. }
  233. /**
  234. * @Given I open the input field for tags in the details view
  235. */
  236. public function iOpenTheInputFieldForTagsInTheDetailsView() {
  237. $this->actor->find(self::fileDetailsInCurrentSectionDetailsViewWithText("Tags"), 10)->click();
  238. }
  239. /**
  240. * @Given I open the :tabName tab in the details view
  241. */
  242. public function iOpenTheTabInTheDetailsView($tabName) {
  243. $this->actor->find(self::tabHeaderInCurrentSectionDetailsViewNamed($tabName), 10)->click();
  244. }
  245. /**
  246. * @Given I share the link for :fileName
  247. */
  248. public function iShareTheLinkFor($fileName) {
  249. $this->actor->find(FileListContext::shareActionForFile(self::currentSectionMainView(), $fileName), 10)->click();
  250. $this->actor->find(self::shareLinkCheckbox(), 5)->click();
  251. }
  252. /**
  253. * @Given I write down the shared link
  254. */
  255. public function iWriteDownTheSharedLink() {
  256. // The shared link field always exists in the DOM (once the "Sharing"
  257. // tab is loaded), but its value is the actual shared link only when it
  258. // is visible.
  259. if (!WaitFor::elementToBeEventuallyShown(
  260. $this->actor,
  261. self::shareLinkField(),
  262. $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
  263. PHPUnit_Framework_Assert::fail("The shared link was not shown yet after $timeout seconds");
  264. }
  265. $this->actor->getSharedNotebook()["shared link"] = $this->actor->find(self::shareLinkField())->getValue();
  266. }
  267. /**
  268. * @When I check the tag :tag in the dropdown for tags in the details view
  269. */
  270. public function iCheckTheTagInTheDropdownForTagsInTheDetailsView($tag) {
  271. $this->iSeeThatTheTagInTheDropdownForTagsInTheDetailsViewIsNotChecked($tag);
  272. $this->actor->find(self::itemInDropdownForTag($tag), 10)->click();
  273. }
  274. /**
  275. * @When I uncheck the tag :tag in the dropdown for tags in the details view
  276. */
  277. public function iUncheckTheTagInTheDropdownForTagsInTheDetailsView($tag) {
  278. $this->iSeeThatTheTagInTheDropdownForTagsInTheDetailsViewIsChecked($tag);
  279. $this->actor->find(self::itemInDropdownForTag($tag), 10)->click();
  280. }
  281. /**
  282. * @When I set the shared link as editable
  283. */
  284. public function iSetTheSharedLinkAsEditable() {
  285. $this->actor->find(self::allowUploadAndEditingRadioButton(), 10)->click();
  286. }
  287. /**
  288. * @When I protect the shared link with the password :password
  289. */
  290. public function iProtectTheSharedLinkWithThePassword($password) {
  291. $this->actor->find(self::passwordProtectCheckbox(), 10)->click();
  292. $this->actor->find(self::passwordProtectField(), 2)->setValue($password . "\r");
  293. }
  294. /**
  295. * @Then I see that the current page is the Files app
  296. */
  297. public function iSeeThatTheCurrentPageIsTheFilesApp() {
  298. PHPUnit_Framework_Assert::assertStringStartsWith(
  299. $this->actor->locatePath("/apps/files/"),
  300. $this->actor->getSession()->getCurrentUrl());
  301. $this->setFileListAncestorForActor(self::currentSectionMainView(), $this->actor);
  302. }
  303. /**
  304. * @Then I see that the details view for :section section is open
  305. */
  306. public function iSeeThatTheDetailsViewForSectionIsOpen($section) {
  307. PHPUnit_Framework_Assert::assertTrue(
  308. $this->actor->find(self::detailsViewForSection($section), 10)->isVisible());
  309. $otherSections = self::sections();
  310. unset($otherSections[$section]);
  311. $this->assertDetailsViewForSectionsAreClosed($otherSections);
  312. }
  313. /**
  314. * @Then I see that the details view is closed
  315. */
  316. public function iSeeThatTheDetailsViewIsClosed() {
  317. PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::currentSectionMainView(), 10));
  318. $this->assertDetailsViewForSectionsAreClosed(self::sections());
  319. }
  320. private function assertDetailsViewForSectionsAreClosed($sections) {
  321. foreach ($sections as $section => $id) {
  322. try {
  323. PHPUnit_Framework_Assert::assertFalse(
  324. $this->actor->find(self::detailsViewForSection($section))->isVisible(),
  325. "Details view for section $section is open but it should be closed");
  326. } catch (NoSuchElementException $exception) {
  327. }
  328. }
  329. }
  330. /**
  331. * @Then I see that the file name shown in the details view is :fileName
  332. */
  333. public function iSeeThatTheFileNameShownInTheDetailsViewIs($fileName) {
  334. PHPUnit_Framework_Assert::assertEquals(
  335. $this->actor->find(self::fileNameInCurrentSectionDetailsView(), 10)->getText(), $fileName);
  336. }
  337. /**
  338. * @Then I see that the input field for tags in the details view is shown
  339. */
  340. public function iSeeThatTheInputFieldForTagsInTheDetailsViewIsShown() {
  341. PHPUnit_Framework_Assert::assertTrue(
  342. $this->actor->find(self::inputFieldForTagsInCurrentSectionDetailsView(), 10)->isVisible());
  343. }
  344. /**
  345. * @Then I see that the input field for tags in the details view contains the tag :tag
  346. */
  347. public function iSeeThatTheInputFieldForTagsInTheDetailsViewContainsTheTag($tag) {
  348. PHPUnit_Framework_Assert::assertTrue(
  349. $this->actor->find(self::itemInInputFieldForTagsInCurrentSectionDetailsViewForTag($tag), 10)->isVisible());
  350. }
  351. /**
  352. * @Then I see that the input field for tags in the details view does not contain the tag :tag
  353. */
  354. public function iSeeThatTheInputFieldForTagsInTheDetailsViewDoesNotContainTheTag($tag) {
  355. $this->iSeeThatTheInputFieldForTagsInTheDetailsViewIsShown();
  356. try {
  357. PHPUnit_Framework_Assert::assertFalse(
  358. $this->actor->find(self::itemInInputFieldForTagsInCurrentSectionDetailsViewForTag($tag))->isVisible());
  359. } catch (NoSuchElementException $exception) {
  360. }
  361. }
  362. /**
  363. * @Then I see that the tag :tag in the dropdown for tags in the details view is checked
  364. */
  365. public function iSeeThatTheTagInTheDropdownForTagsInTheDetailsViewIsChecked($tag) {
  366. PHPUnit_Framework_Assert::assertTrue(
  367. $this->actor->find(self::checkmarkInItemInDropdownForTag($tag), 10)->isVisible());
  368. }
  369. /**
  370. * @Then I see that the tag :tag in the dropdown for tags in the details view is not checked
  371. */
  372. public function iSeeThatTheTagInTheDropdownForTagsInTheDetailsViewIsNotChecked($tag) {
  373. PHPUnit_Framework_Assert::assertTrue(
  374. $this->actor->find(self::itemInDropdownForTag($tag), 10)->isVisible());
  375. PHPUnit_Framework_Assert::assertFalse(
  376. $this->actor->find(self::checkmarkInItemInDropdownForTag($tag))->isVisible());
  377. }
  378. /**
  379. * @When I see that the :tabName tab in the details view is eventually loaded
  380. */
  381. public function iSeeThatTheTabInTheDetailsViewIsEventuallyLoaded($tabName) {
  382. if (!WaitFor::elementToBeEventuallyNotShown(
  383. $this->actor,
  384. self::loadingIconForTabInCurrentSectionDetailsViewNamed($tabName),
  385. $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
  386. PHPUnit_Framework_Assert::fail("The $tabName tab in the details view has not been loaded after $timeout seconds");
  387. }
  388. }
  389. /**
  390. * @Then I see that the working icon for password protect is shown
  391. */
  392. public function iSeeThatTheWorkingIconForPasswordProtectIsShown() {
  393. PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::passwordProtectWorkingIcon(), 10));
  394. }
  395. /**
  396. * @Then I see that the working icon for password protect is eventually not shown
  397. */
  398. public function iSeeThatTheWorkingIconForPasswordProtectIsEventuallyNotShown() {
  399. if (!WaitFor::elementToBeEventuallyNotShown(
  400. $this->actor,
  401. self::passwordProtectWorkingIcon(),
  402. $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
  403. PHPUnit_Framework_Assert::fail("The working icon for password protect is still shown after $timeout seconds");
  404. }
  405. }
  406. /**
  407. * @Given I share the link for :fileName protected by the password :password
  408. */
  409. public function iShareTheLinkForProtectedByThePassword($fileName, $password) {
  410. $this->iShareTheLinkFor($fileName);
  411. $this->iProtectTheSharedLinkWithThePassword($password);
  412. $this->iSeeThatTheWorkingIconForPasswordProtectIsShown();
  413. $this->iSeeThatTheWorkingIconForPasswordProtectIsEventuallyNotShown();
  414. }
  415. }