Mercurial > hg > isophonics-drupal-site
comparison core/tests/Drupal/FunctionalJavascriptTests/TableDrag/TableDragTest.php @ 17:129ea1e6d783
Update, including to Drupal core 8.6.10
author | Chris Cannam |
---|---|
date | Thu, 28 Feb 2019 13:21:36 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
16:c2387f117808 | 17:129ea1e6d783 |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\FunctionalJavascriptTests\TableDrag; | |
4 | |
5 use Behat\Mink\Element\NodeElement; | |
6 use Drupal\FunctionalJavascriptTests\WebDriverTestBase; | |
7 | |
8 /** | |
9 * Tests draggable table. | |
10 * | |
11 * @group javascript | |
12 */ | |
13 class TableDragTest extends WebDriverTestBase { | |
14 | |
15 /** | |
16 * Class used to verify that dragging operations are in execution. | |
17 */ | |
18 const DRAGGING_CSS_CLASS = 'tabledrag-test-dragging'; | |
19 | |
20 /** | |
21 * {@inheritdoc} | |
22 */ | |
23 protected static $modules = ['tabledrag_test']; | |
24 | |
25 /** | |
26 * The state service. | |
27 * | |
28 * @var \Drupal\Core\State\StateInterface | |
29 */ | |
30 protected $state; | |
31 | |
32 /** | |
33 * {@inheritdoc} | |
34 */ | |
35 protected function setUp() { | |
36 parent::setUp(); | |
37 | |
38 $this->state = $this->container->get('state'); | |
39 } | |
40 | |
41 /** | |
42 * Tests accessibility through keyboard of the tabledrag functionality. | |
43 */ | |
44 public function testKeyboardAccessibility() { | |
45 $this->state->set('tabledrag_test_table', array_flip(range(1, 5))); | |
46 | |
47 $expected_table = [ | |
48 ['id' => 1, 'weight' => 0, 'parent' => '', 'indentation' => 0, 'changed' => FALSE], | |
49 ['id' => 2, 'weight' => 0, 'parent' => '', 'indentation' => 0, 'changed' => FALSE], | |
50 ['id' => 3, 'weight' => 0, 'parent' => '', 'indentation' => 0, 'changed' => FALSE], | |
51 ['id' => 4, 'weight' => 0, 'parent' => '', 'indentation' => 0, 'changed' => FALSE], | |
52 ['id' => 5, 'weight' => 0, 'parent' => '', 'indentation' => 0, 'changed' => FALSE], | |
53 ]; | |
54 $this->drupalGet('tabledrag_test'); | |
55 $this->assertDraggableTable($expected_table); | |
56 | |
57 // Nest the row with id 2 as child of row 1. | |
58 $this->moveRowWithKeyboard($this->findRowById(2), 'right'); | |
59 $expected_table[1] = ['id' => 2, 'weight' => -10, 'parent' => 1, 'indentation' => 1, 'changed' => TRUE]; | |
60 $this->assertDraggableTable($expected_table); | |
61 | |
62 // Nest the row with id 3 as child of row 1. | |
63 $this->moveRowWithKeyboard($this->findRowById(3), 'right'); | |
64 $expected_table[2] = ['id' => 3, 'weight' => -9, 'parent' => 1, 'indentation' => 1, 'changed' => TRUE]; | |
65 $this->assertDraggableTable($expected_table); | |
66 | |
67 // Nest the row with id 3 as child of row 2. | |
68 $this->moveRowWithKeyboard($this->findRowById(3), 'right'); | |
69 $expected_table[2] = ['id' => 3, 'weight' => -10, 'parent' => 2, 'indentation' => 2, 'changed' => TRUE]; | |
70 $this->assertDraggableTable($expected_table); | |
71 | |
72 // Nesting should be allowed to maximum level 2. | |
73 $this->moveRowWithKeyboard($this->findRowById(4), 'right', 4); | |
74 $expected_table[3] = ['id' => 4, 'weight' => -9, 'parent' => 2, 'indentation' => 2, 'changed' => TRUE]; | |
75 $this->assertDraggableTable($expected_table); | |
76 | |
77 // Re-order children of row 1. | |
78 $this->moveRowWithKeyboard($this->findRowById(4), 'up'); | |
79 $expected_table[2] = ['id' => 4, 'weight' => -10, 'parent' => 2, 'indentation' => 2, 'changed' => TRUE]; | |
80 $expected_table[3] = ['id' => 3, 'weight' => -9, 'parent' => 2, 'indentation' => 2, 'changed' => TRUE]; | |
81 $this->assertDraggableTable($expected_table); | |
82 | |
83 // Move back the row 3 to the 1st level. | |
84 $this->moveRowWithKeyboard($this->findRowById(3), 'left'); | |
85 $expected_table[3] = ['id' => 3, 'weight' => -9, 'parent' => 1, 'indentation' => 1, 'changed' => TRUE]; | |
86 $this->assertDraggableTable($expected_table); | |
87 | |
88 $this->moveRowWithKeyboard($this->findRowById(3), 'left'); | |
89 $expected_table[0] = ['id' => 1, 'weight' => -10, 'parent' => '', 'indentation' => 0, 'changed' => FALSE]; | |
90 $expected_table[3] = ['id' => 3, 'weight' => -9, 'parent' => '', 'indentation' => 0, 'changed' => TRUE]; | |
91 $expected_table[4] = ['id' => 5, 'weight' => -8, 'parent' => '', 'indentation' => 0, 'changed' => FALSE]; | |
92 $this->assertDraggableTable($expected_table); | |
93 | |
94 // Move row 3 to the last position. | |
95 $this->moveRowWithKeyboard($this->findRowById(3), 'down'); | |
96 $expected_table[3] = ['id' => 5, 'weight' => -9, 'parent' => '', 'indentation' => 0, 'changed' => FALSE]; | |
97 $expected_table[4] = ['id' => 3, 'weight' => -8, 'parent' => '', 'indentation' => 0, 'changed' => TRUE]; | |
98 $this->assertDraggableTable($expected_table); | |
99 | |
100 // Nothing happens when trying to move the last row further down. | |
101 $this->moveRowWithKeyboard($this->findRowById(3), 'down'); | |
102 $this->assertDraggableTable($expected_table); | |
103 | |
104 // Nest row 3 under 5. The max depth allowed should be 1. | |
105 $this->moveRowWithKeyboard($this->findRowById(3), 'right', 3); | |
106 $expected_table[4] = ['id' => 3, 'weight' => -10, 'parent' => 5, 'indentation' => 1, 'changed' => TRUE]; | |
107 $this->assertDraggableTable($expected_table); | |
108 | |
109 // The first row of the table cannot be nested. | |
110 $this->moveRowWithKeyboard($this->findRowById(1), 'right'); | |
111 $this->assertDraggableTable($expected_table); | |
112 | |
113 // Move a row which has nested children. The children should move with it, | |
114 // with nesting preserved. Swap the order of the top-level rows by moving | |
115 // row 1 to after row 3. | |
116 $this->moveRowWithKeyboard($this->findRowById(1), 'down', 2); | |
117 $expected_table[0] = ['id' => 5, 'weight' => -10, 'parent' => '', 'indentation' => 0, 'changed' => FALSE]; | |
118 $expected_table[3] = $expected_table[1]; | |
119 $expected_table[1] = $expected_table[4]; | |
120 $expected_table[4] = $expected_table[2]; | |
121 $expected_table[2] = ['id' => 1, 'weight' => -9, 'parent' => '', 'indentation' => 0, 'changed' => TRUE]; | |
122 $this->assertDraggableTable($expected_table); | |
123 } | |
124 | |
125 /** | |
126 * Tests the root and leaf behaviors for rows. | |
127 */ | |
128 public function testRootLeafDraggableRowsWithKeyboard() { | |
129 $this->state->set('tabledrag_test_table', [ | |
130 1 => [], | |
131 2 => ['parent' => 1, 'depth' => 1, 'classes' => ['tabledrag-leaf']], | |
132 3 => ['parent' => 1, 'depth' => 1], | |
133 4 => [], | |
134 5 => ['classes' => ['tabledrag-root']], | |
135 ]); | |
136 | |
137 $this->drupalGet('tabledrag_test'); | |
138 $expected_table = [ | |
139 ['id' => 1, 'weight' => 0, 'parent' => '', 'indentation' => 0, 'changed' => FALSE], | |
140 ['id' => 2, 'weight' => 0, 'parent' => 1, 'indentation' => 1, 'changed' => FALSE], | |
141 ['id' => 3, 'weight' => 0, 'parent' => 1, 'indentation' => 1, 'changed' => FALSE], | |
142 ['id' => 4, 'weight' => 0, 'parent' => '', 'indentation' => 0, 'changed' => FALSE], | |
143 ['id' => 5, 'weight' => 0, 'parent' => '', 'indentation' => 0, 'changed' => FALSE], | |
144 ]; | |
145 $this->assertDraggableTable($expected_table); | |
146 | |
147 // Rows marked as root cannot be moved as children of another row. | |
148 $this->moveRowWithKeyboard($this->findRowById(5), 'right'); | |
149 $this->assertDraggableTable($expected_table); | |
150 | |
151 // Rows marked as leaf cannot have children. Trying to move the row #3 | |
152 // as child of #2 should have no results. | |
153 $this->moveRowWithKeyboard($this->findRowById(3), 'right'); | |
154 $this->assertDraggableTable($expected_table); | |
155 | |
156 // Leaf can be still swapped and moved to first level. | |
157 $this->moveRowWithKeyboard($this->findRowById(2), 'down'); | |
158 $this->moveRowWithKeyboard($this->findRowById(2), 'left'); | |
159 $expected_table[0]['weight'] = -10; | |
160 $expected_table[1]['id'] = 3; | |
161 $expected_table[1]['weight'] = -10; | |
162 $expected_table[2] = ['id' => 2, 'weight' => -9, 'parent' => '', 'indentation' => 0, 'changed' => TRUE]; | |
163 $expected_table[3]['weight'] = -8; | |
164 $expected_table[4]['weight'] = -7; | |
165 $this->assertDraggableTable($expected_table); | |
166 | |
167 // Root rows can have children. | |
168 $this->moveRowWithKeyboard($this->findRowById(4), 'down'); | |
169 $this->moveRowWithKeyboard($this->findRowById(4), 'right'); | |
170 $expected_table[3]['id'] = 5; | |
171 $expected_table[4] = ['id' => 4, 'weight' => -10, 'parent' => 5, 'indentation' => 1, 'changed' => TRUE]; | |
172 $this->assertDraggableTable($expected_table); | |
173 } | |
174 | |
175 /** | |
176 * Tests the warning that appears upon making changes to a tabledrag table. | |
177 */ | |
178 public function testTableDragChangedWarning() { | |
179 $this->drupalGet('tabledrag_test'); | |
180 | |
181 // By default no text is visible. | |
182 $this->assertSession()->pageTextNotContains('You have unsaved changes.'); | |
183 // Try to make a non-allowed action, like moving further down the last row. | |
184 // No changes happen, so no message should be shown. | |
185 $this->moveRowWithKeyboard($this->findRowById(5), 'down'); | |
186 $this->assertSession()->pageTextNotContains('You have unsaved changes.'); | |
187 | |
188 // Make a change. The message will appear. | |
189 $this->moveRowWithKeyboard($this->findRowById(5), 'right'); | |
190 $this->assertSession()->pageTextContainsOnce('You have unsaved changes.'); | |
191 | |
192 // Make another change, the text will stay visible and appear only once. | |
193 $this->moveRowWithKeyboard($this->findRowById(2), 'up'); | |
194 $this->assertSession()->pageTextContainsOnce('You have unsaved changes.'); | |
195 } | |
196 | |
197 /** | |
198 * Asserts the whole structure of the draggable test table. | |
199 * | |
200 * @param array $structure | |
201 * The table structure. Each entry represents a row and consists of: | |
202 * - id: the expected value for the ID hidden field. | |
203 * - weight: the expected row weight. | |
204 * - parent: the expected parent ID for the row. | |
205 * - indentation: how many indents the row should have. | |
206 * - changed: whether or not the row should have been marked as changed. | |
207 */ | |
208 protected function assertDraggableTable(array $structure) { | |
209 $rows = $this->getSession()->getPage()->findAll('xpath', '//table[@id="tabledrag-test-table"]/tbody/tr'); | |
210 $this->assertSession()->elementsCount('xpath', '//table[@id="tabledrag-test-table"]/tbody/tr', count($structure)); | |
211 | |
212 foreach ($structure as $delta => $expected) { | |
213 $this->assertTableRow($rows[$delta], $expected['id'], $expected['weight'], $expected['parent'], $expected['indentation'], $expected['changed']); | |
214 } | |
215 } | |
216 | |
217 /** | |
218 * Asserts the values of a draggable row. | |
219 * | |
220 * @param \Behat\Mink\Element\NodeElement $row | |
221 * The row element to assert. | |
222 * @param string $id | |
223 * The expected value for the ID hidden input of the row. | |
224 * @param int $weight | |
225 * The expected weight of the row. | |
226 * @param string $parent | |
227 * The expected parent ID. | |
228 * @param int $indentation | |
229 * The expected indentation of the row. | |
230 * @param bool $changed | |
231 * Whether or not the row should have been marked as changed. | |
232 */ | |
233 protected function assertTableRow(NodeElement $row, $id, $weight, $parent = '', $indentation = 0, $changed = FALSE) { | |
234 // Assert that the row position is correct by checking that the id | |
235 // corresponds. | |
236 $this->assertSession()->hiddenFieldValueEquals("table[$id][id]", $id, $row); | |
237 $this->assertSession()->hiddenFieldValueEquals("table[$id][parent]", $parent, $row); | |
238 $this->assertSession()->fieldValueEquals("table[$id][weight]", $weight, $row); | |
239 $this->assertSession()->elementsCount('css', '.js-indentation.indentation', $indentation, $row); | |
240 // A row is marked as changed when the related markup is present. | |
241 $this->assertSession()->elementsCount('css', 'abbr.tabledrag-changed', (int) $changed, $row); | |
242 } | |
243 | |
244 /** | |
245 * Finds a row in the test table by the row ID. | |
246 * | |
247 * @param string $id | |
248 * The ID of the row. | |
249 * | |
250 * @return \Behat\Mink\Element\NodeElement | |
251 * The row element. | |
252 */ | |
253 protected function findRowById($id) { | |
254 $xpath = "//table[@id='tabledrag-test-table']/tbody/tr[.//input[@name='table[$id][id]']]"; | |
255 $row = $this->getSession()->getPage()->find('xpath', $xpath); | |
256 $this->assertNotEmpty($row); | |
257 return $row; | |
258 } | |
259 | |
260 /** | |
261 * Moves a row through the keyboard. | |
262 * | |
263 * @param \Behat\Mink\Element\NodeElement $row | |
264 * The row to move. | |
265 * @param string $arrow | |
266 * The arrow button to use to move the row. Either one of 'left', 'right', | |
267 * 'up' or 'down'. | |
268 * @param int $repeat | |
269 * (optional) How many times to press the arrow button. Defaults to 1. | |
270 */ | |
271 protected function moveRowWithKeyboard(NodeElement $row, $arrow, $repeat = 1) { | |
272 $keys = [ | |
273 'left' => 37, | |
274 'right' => 39, | |
275 'up' => 38, | |
276 'down' => 40, | |
277 ]; | |
278 if (!isset($keys[$arrow])) { | |
279 throw new \InvalidArgumentException('The arrow parameter must be one of "left", "right", "up" or "down".'); | |
280 } | |
281 | |
282 $key = $keys[$arrow]; | |
283 | |
284 $handle = $row->find('css', 'a.tabledrag-handle'); | |
285 $handle->focus(); | |
286 | |
287 for ($i = 0; $i < $repeat; $i++) { | |
288 $this->markRowHandleForDragging($handle); | |
289 $handle->keyDown($key); | |
290 $handle->keyUp($key); | |
291 $this->waitUntilDraggingCompleted($handle); | |
292 } | |
293 | |
294 $handle->blur(); | |
295 } | |
296 | |
297 /** | |
298 * Marks a row handle for dragging. | |
299 * | |
300 * The handle is marked by adding a css class that is removed by an helper | |
301 * js library once the dragging is over. | |
302 * | |
303 * @param \Behat\Mink\Element\NodeElement $handle | |
304 * The draggable row handle element. | |
305 * | |
306 * @throws \Exception | |
307 * Thrown when the class is not added successfully to the handle. | |
308 */ | |
309 protected function markRowHandleForDragging(NodeElement $handle) { | |
310 $class = self::DRAGGING_CSS_CLASS; | |
311 $script = <<<JS | |
312 document.evaluate("{$handle->getXpath()}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null) | |
313 .singleNodeValue.classList.add('{$class}'); | |
314 JS; | |
315 | |
316 $this->getSession()->executeScript($script); | |
317 $has_class = $this->getSession()->getPage()->waitFor(1, function () use ($handle, $class) { | |
318 return $handle->hasClass($class); | |
319 }); | |
320 | |
321 if (!$has_class) { | |
322 throw new \Exception(sprintf('Dragging css class was not added on handle "%s".', $handle->getXpath())); | |
323 } | |
324 } | |
325 | |
326 /** | |
327 * Waits until the dragging operations are finished on a row handle. | |
328 * | |
329 * @param \Behat\Mink\Element\NodeElement $handle | |
330 * The draggable row handle element. | |
331 * | |
332 * @throws \Exception | |
333 * Thrown when the dragging operations are not completed on time. | |
334 */ | |
335 protected function waitUntilDraggingCompleted(NodeElement $handle) { | |
336 $class_removed = $this->getSession()->getPage()->waitFor(1, function () use ($handle) { | |
337 return !$handle->hasClass($this::DRAGGING_CSS_CLASS); | |
338 }); | |
339 | |
340 if (!$class_removed) { | |
341 throw new \Exception(sprintf('Dragging operations did not complete on time on handle %s', $handle->getXpath())); | |
342 } | |
343 } | |
344 | |
345 } |