FilesAppContext.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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. use PHPUnit\Framework\Assert;
  24. class FilesAppContext implements Context, ActorAwareInterface {
  25. use ActorAware;
  26. use FileListAncestorSetter;
  27. /**
  28. * @return array
  29. */
  30. public static function sections() {
  31. return [ "All files" => "files",
  32. "Recent" => "recent",
  33. "Favorites" => "favorites",
  34. "Shared with you" => "sharingin",
  35. "Shared with others" => "sharingout",
  36. "Shared by link" => "sharinglinks",
  37. "Tags" => "systemtagsfilter",
  38. "Deleted files" => "trashbin" ];
  39. }
  40. /**
  41. * @return Locator
  42. */
  43. private static function appMenu() {
  44. return Locator::forThe()->css("header nav.app-menu")->
  45. describedAs("App menu in header");
  46. }
  47. /**
  48. * @return Locator
  49. */
  50. public static function filesItemInAppMenu() {
  51. return Locator::forThe()->xpath("//li[@data-app-id = 'files']")->
  52. descendantOf(self::appMenu())->
  53. describedAs("Files item in app menu in header");
  54. }
  55. /**
  56. * @return Locator
  57. */
  58. public static function mainViewForSection($section) {
  59. $sectionId = self::sections()[$section];
  60. return Locator::forThe()->id("app-content-$sectionId")->
  61. describedAs("Main view for section $section in Files app");
  62. }
  63. /**
  64. * @return Locator
  65. */
  66. public static function currentSectionMainView() {
  67. return Locator::forThe()->xpath("//*[starts-with(@id, 'app-content-') and not(@id = 'app-content-vue') and not(contains(concat(' ', normalize-space(@class), ' '), ' hidden '))]")->
  68. describedAs("Current section main view in Files app");
  69. }
  70. /**
  71. * @return Locator
  72. */
  73. public static function detailsView() {
  74. return Locator::forThe()->xpath("//*[@id=\"app-sidebar\" or contains(@class, 'app-sidebar')]")->
  75. describedAs("Details view in Files app");
  76. }
  77. /**
  78. * @return Locator
  79. */
  80. public static function closeDetailsViewButton() {
  81. return Locator::forThe()->css(".app-sidebar__close")->
  82. descendantOf(self::detailsView())->
  83. describedAs("Close details view in Files app");
  84. }
  85. /**
  86. * @return Locator
  87. */
  88. public static function fileNameInDetailsView() {
  89. return Locator::forThe()->css(".app-sidebar-header__title")->
  90. descendantOf(self::detailsView())->
  91. describedAs("File name in details view in Files app");
  92. }
  93. /**
  94. * @return Locator
  95. */
  96. public static function favoriteActionInFileDetailsInDetailsView() {
  97. return Locator::forThe()->css(".app-sidebar-header__star")->
  98. descendantOf(self::fileDetailsInDetailsView())->
  99. describedAs("Favorite action in file details in details view in Files app");
  100. }
  101. /**
  102. * @return Locator
  103. */
  104. public static function notFavoritedStateIconInFileDetailsInDetailsView() {
  105. return Locator::forThe()->css(".star-outline-icon")->
  106. descendantOf(self::favoriteActionInFileDetailsInDetailsView())->
  107. describedAs("Not favorited state icon in file details in details view in Files app");
  108. }
  109. /**
  110. * @return Locator
  111. */
  112. public static function favoritedStateIconInFileDetailsInDetailsView() {
  113. return Locator::forThe()->css(".star-icon")->
  114. descendantOf(self::favoriteActionInFileDetailsInDetailsView())->
  115. describedAs("Favorited state icon in file details in details view in Files app");
  116. }
  117. /**
  118. * @return Locator
  119. */
  120. public static function fileDetailsInDetailsViewWithText($fileDetailsText) {
  121. return Locator::forThe()->xpath("//span[normalize-space() = '$fileDetailsText']")->
  122. descendantOf(self::fileDetailsInDetailsView())->
  123. describedAs("File details with text \"$fileDetailsText\" in details view in Files app");
  124. }
  125. /**
  126. * @return Locator
  127. */
  128. private static function fileDetailsInDetailsView() {
  129. return Locator::forThe()->css(".app-sidebar-header__desc")->
  130. descendantOf(self::detailsView())->
  131. describedAs("File details in details view in Files app");
  132. }
  133. /**
  134. * @return Locator
  135. */
  136. public static function inputFieldForTagsInDetailsView() {
  137. return Locator::forThe()->css(".systemTagsInfoView")->
  138. descendantOf(self::detailsView())->
  139. describedAs("Input field for tags in details view in Files app");
  140. }
  141. /**
  142. * @return Locator
  143. */
  144. public static function itemInInputFieldForTagsInDetailsViewForTag($tag) {
  145. return Locator::forThe()->xpath("//span[normalize-space() = '$tag']")->
  146. descendantOf(self::inputFieldForTagsInDetailsView())->
  147. describedAs("Item in input field for tags in details view for tag $tag in Files app");
  148. }
  149. /**
  150. * @return Locator
  151. */
  152. public static function itemInDropdownForTag($tag) {
  153. return Locator::forThe()->xpath("//*[contains(concat(' ', normalize-space(@class), ' '), ' select2-result-label ')]//span[normalize-space() = '$tag']/ancestor::li")->
  154. descendantOf(self::select2Dropdown())->
  155. describedAs("Item in dropdown for tag $tag in Files app");
  156. }
  157. /**
  158. * @return Locator
  159. */
  160. public static function checkmarkInItemInDropdownForTag($tag) {
  161. return Locator::forThe()->css(".checkmark")->
  162. descendantOf(self::itemInDropdownForTag($tag))->
  163. describedAs("Checkmark in item in dropdown for tag $tag in Files app");
  164. }
  165. /**
  166. * @return Locator
  167. */
  168. private static function select2Dropdown() {
  169. return Locator::forThe()->css("#select2-drop")->
  170. describedAs("Select2 dropdown in Files app");
  171. }
  172. /**
  173. * @return Locator
  174. */
  175. public static function tabHeaderInDetailsViewNamed($tabHeaderName) {
  176. return Locator::forThe()->xpath("//span[contains(@class, 'app-sidebar-tabs__tab') and normalize-space() = '$tabHeaderName']")->
  177. descendantOf(self::tabHeadersInDetailsView())->
  178. describedAs("Tab header named $tabHeaderName in details view in Files app");
  179. }
  180. /**
  181. * @return Locator
  182. */
  183. private static function tabHeadersInDetailsView() {
  184. return Locator::forThe()->css(".app-sidebar-tabs__nav")->
  185. descendantOf(self::detailsView())->
  186. describedAs("Tab headers in details view in Files app");
  187. }
  188. /**
  189. * @return Locator
  190. */
  191. public static function tabInDetailsViewNamed($tabName) {
  192. return Locator::forThe()->xpath("//div[contains(concat(' ', normalize-space(@class), ' '), ' app-sidebar-tabs__content ')]/section[@aria-labelledby = '$tabName' and @role = 'tabpanel']")->
  193. descendantOf(self::detailsView())->
  194. describedAs("Tab named $tabName in details view in Files app");
  195. }
  196. /**
  197. * @return Locator
  198. */
  199. public static function loadingIconForTabInDetailsViewNamed($tabName) {
  200. return Locator::forThe()->css(".icon-loading")->
  201. descendantOf(self::tabInDetailsViewNamed($tabName))->
  202. describedAs("Loading icon for tab named $tabName in details view in Files app");
  203. }
  204. /**
  205. * @Given I open the Files app
  206. */
  207. public function iOpenTheFilesApp() {
  208. $this->actor->find(self::filesItemInAppMenu(), 10)->click();
  209. }
  210. /**
  211. * @Given I close the details view
  212. */
  213. public function iCloseTheDetailsView() {
  214. $this->actor->find(self::closeDetailsViewButton(), 10)->click();
  215. }
  216. /**
  217. * @Given I open the input field for tags in the details view
  218. */
  219. public function iOpenTheInputFieldForTagsInTheDetailsView() {
  220. $this->actor->find(self::fileDetailsInDetailsViewWithText("Tags"), 10)->click();
  221. }
  222. /**
  223. * @Given I open the :tabName tab in the details view
  224. */
  225. public function iOpenTheTabInTheDetailsView($tabName) {
  226. $this->actor->find(self::tabHeaderInDetailsViewNamed($tabName), 10)->click();
  227. }
  228. /**
  229. * @When I mark the file as favorite in the details view
  230. */
  231. public function iMarkTheFileAsFavoriteInTheDetailsView() {
  232. $this->iSeeThatTheFileIsNotMarkedAsFavoriteInTheDetailsView();
  233. $this->actor->find(self::favoriteActionInFileDetailsInDetailsView(), 10)->click();
  234. }
  235. /**
  236. * @When I unmark the file as favorite in the details view
  237. */
  238. public function iUnmarkTheFileAsFavoriteInTheDetailsView() {
  239. $this->iSeeThatTheFileIsMarkedAsFavoriteInTheDetailsView();
  240. $this->actor->find(self::favoriteActionInFileDetailsInDetailsView(), 10)->click();
  241. }
  242. /**
  243. * @When I check the tag :tag in the dropdown for tags in the details view
  244. */
  245. public function iCheckTheTagInTheDropdownForTagsInTheDetailsView($tag) {
  246. $this->iSeeThatTheTagInTheDropdownForTagsInTheDetailsViewIsNotChecked($tag);
  247. $this->actor->find(self::itemInDropdownForTag($tag), 10)->click();
  248. }
  249. /**
  250. * @When I uncheck the tag :tag in the dropdown for tags in the details view
  251. */
  252. public function iUncheckTheTagInTheDropdownForTagsInTheDetailsView($tag) {
  253. $this->iSeeThatTheTagInTheDropdownForTagsInTheDetailsViewIsChecked($tag);
  254. $this->actor->find(self::itemInDropdownForTag($tag), 10)->click();
  255. }
  256. /**
  257. * @Then I see that the current page is the Files app
  258. */
  259. public function iSeeThatTheCurrentPageIsTheFilesApp() {
  260. Assert::assertStringStartsWith(
  261. $this->actor->locatePath("/apps/files/"),
  262. $this->actor->getSession()->getCurrentUrl());
  263. $this->setFileListAncestorForActor(self::currentSectionMainView(), $this->actor);
  264. }
  265. /**
  266. * @Then I see that the details view is open
  267. */
  268. public function iSeeThatTheDetailsViewIsOpen() {
  269. // The sidebar always exists in the DOM, so it has to be explicitly
  270. // waited for it to be visible instead of relying on the implicit wait
  271. // made to find the element.
  272. if (!WaitFor::elementToBeEventuallyShown(
  273. $this->actor,
  274. self::detailsView(),
  275. $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
  276. Assert::fail("The details view is not open yet after $timeout seconds");
  277. }
  278. }
  279. /**
  280. * @Then I see that the details view is closed
  281. */
  282. public function iSeeThatTheDetailsViewIsClosed() {
  283. if (!WaitFor::elementToBeEventuallyNotShown(
  284. $this->actor,
  285. self::detailsView(),
  286. $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
  287. Assert::fail("The details view is not closed yet after $timeout seconds");
  288. }
  289. }
  290. /**
  291. * @Then I see that the file name shown in the details view is :fileName
  292. */
  293. public function iSeeThatTheFileNameShownInTheDetailsViewIs($fileName) {
  294. Assert::assertEquals(
  295. $this->actor->find(self::fileNameInDetailsView(), 10)->getText(), $fileName);
  296. }
  297. /**
  298. * @Then I see that the file is marked as favorite in the details view
  299. */
  300. public function iSeeThatTheFileIsMarkedAsFavoriteInTheDetailsView() {
  301. Assert::assertNotNull(
  302. $this->actor->find(self::favoritedStateIconInFileDetailsInDetailsView(), 10));
  303. }
  304. /**
  305. * @Then I see that the file is not marked as favorite in the details view
  306. */
  307. public function iSeeThatTheFileIsNotMarkedAsFavoriteInTheDetailsView() {
  308. Assert::assertNotNull(
  309. $this->actor->find(self::notFavoritedStateIconInFileDetailsInDetailsView(), 10));
  310. }
  311. /**
  312. * @Then I see that the input field for tags in the details view is shown
  313. */
  314. public function iSeeThatTheInputFieldForTagsInTheDetailsViewIsShown() {
  315. Assert::assertTrue(
  316. $this->actor->find(self::inputFieldForTagsInDetailsView(), 10)->isVisible());
  317. }
  318. /**
  319. * @Then I see that the input field for tags in the details view contains the tag :tag
  320. */
  321. public function iSeeThatTheInputFieldForTagsInTheDetailsViewContainsTheTag($tag) {
  322. Assert::assertTrue(
  323. $this->actor->find(self::itemInInputFieldForTagsInDetailsViewForTag($tag), 10)->isVisible());
  324. }
  325. /**
  326. * @Then I see that the input field for tags in the details view does not contain the tag :tag
  327. */
  328. public function iSeeThatTheInputFieldForTagsInTheDetailsViewDoesNotContainTheTag($tag) {
  329. $this->iSeeThatTheInputFieldForTagsInTheDetailsViewIsShown();
  330. try {
  331. Assert::assertFalse(
  332. $this->actor->find(self::itemInInputFieldForTagsInDetailsViewForTag($tag))->isVisible());
  333. } catch (NoSuchElementException $exception) {
  334. }
  335. }
  336. /**
  337. * @Then I see that the tag :tag in the dropdown for tags in the details view is checked
  338. */
  339. public function iSeeThatTheTagInTheDropdownForTagsInTheDetailsViewIsChecked($tag) {
  340. Assert::assertTrue(
  341. $this->actor->find(self::checkmarkInItemInDropdownForTag($tag), 10)->isVisible());
  342. }
  343. /**
  344. * @Then I see that the tag :tag in the dropdown for tags in the details view is not checked
  345. */
  346. public function iSeeThatTheTagInTheDropdownForTagsInTheDetailsViewIsNotChecked($tag) {
  347. Assert::assertTrue(
  348. $this->actor->find(self::itemInDropdownForTag($tag), 10)->isVisible());
  349. Assert::assertFalse(
  350. $this->actor->find(self::checkmarkInItemInDropdownForTag($tag))->isVisible());
  351. }
  352. /**
  353. * @When I see that the :tabName tab in the details view is eventually loaded
  354. */
  355. public function iSeeThatTheTabInTheDetailsViewIsEventuallyLoaded($tabName) {
  356. if (!WaitFor::elementToBeEventuallyNotShown(
  357. $this->actor,
  358. self::loadingIconForTabInDetailsViewNamed($tabName),
  359. $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
  360. Assert::fail("The $tabName tab in the details view has not been loaded after $timeout seconds");
  361. }
  362. }
  363. }