Bug 1874684 - Part 17: Fix uninitialised variable warnings from clang-tidy. r=allstarschh
[gecko.git] / editor / libeditor / tests / test_pasting_table_rows.html
blob328fb0b2979518e7f5d8e9d1a81ef874317fad1c
1 <!DOCTYPE HTML>
2 <html>
3 <head>
4 <meta charset="utf-8">
5 <title>Test pasting table rows</title>
6 <script src="/tests/SimpleTest/SimpleTest.js"></script>
7 <script src="/tests/SimpleTest/EventUtils.js"></script>
8 <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
9 <style>
10 /**
11 * A small font-size, so that the loaded document fits on the screens of all
12 * test devices.
14 * { font-size: 8px; }
16 /**
17 * Helps fitting the tables on the screens of all test devices.
19 div[class="tableContainer"] {
20 display: inline-block;
22 </style>
23 <script>
24 const kEditabilityModeContenteditable = "contenteditable";
25 const kEditabilityModeDesignMode = "designMode";
27 // All column names of the test-tables used below.
28 const kColumns = ["c1", "c2", "c3"];
30 // Ctrl+click on table cells to select them.
31 const kSelectionModeClickSelection = "click-selection";
32 // Click and drag from the first given row to the end of the last given row.
33 const kSelectionModeDragSelection = "drag-selection";
35 const kTableTagName = "TABLE";
36 const kTbodyTagName = "TBODY";
37 const kTheadTagName = "THEAD";
38 const kTfootTagName = "TFOOT";
40 const kInputEventType = "input";
41 const kInputEventInputTypeInsertFromPaste = "insertFromPaste";
43 // Where a table is pasted to in the test.
44 const kTargetElementId = "targetElement";
46 /**
47 * @param aTableName see Test::constructor::aTableName.
48 * @param aRowsInTable see Test::constructor::aRowsInTable.
49 * @return an array of elements of aRowsInTable.
51 function FilterRowsWithParentTag(aTableName, aRowsInTable, aTagName) {
52 return aRowsInTable.filter(rowName => document.getElementById(aTableName +
53 rowName).parentElement.tagName == aTagName);
56 /**
57 * Tables used with this class are required to:
58 * - have ids of the following form for each table cell:
59 <tableName><rowName><column>. Where <column> has to be one of
60 `kColumns`.
61 - have exactly `kColumns.length` columns per row.
62 - have an id of the form <tableName><rowName> for each table row.
64 class Test {
65 /**
66 * @param aTableName indicates which table to operate on.
67 * @param aRowsInTable an array of row names. Ordered from top to bottom.
68 * @param aEditabilityMode `kEditabilityModeContenteditable` or
69 * `kEditabilityModeDesignMode`.
70 * @param aSelectionMode `kSelectionModeClickSelection` or
71 * `kSelectionModeDragSelection`.
73 constructor(aTableName, aRowsInTable, aEditabilityMode, aSelectionMode) {
74 ok(aEditabilityMode == kEditabilityModeContenteditable ||
75 aEditabilityMode == kEditabilityModeDesignMode,
76 "Editablity mode is valid.");
78 ok(aSelectionMode == kSelectionModeClickSelection ||
79 aSelectionMode == kSelectionModeDragSelection,
80 "Selection mode is valid.");
82 this._tableName = aTableName;
83 this._rowsInTable = aRowsInTable;
84 this._editabilityMode = aEditabilityMode;
85 this._selectionMode = aSelectionMode;
86 this._innerHTMLOfTargetBeforeTestRun =
87 document.getElementById(kTargetElementId).innerHTML;
89 if (this._editabilityMode == kEditabilityModeDesignMode) {
90 this._removeContenteditableAttributeOfTarget();
91 document.designMode = "on";
94 SimpleTest.info("Constructed the test (" + this._toString() + ").");
97 /**
98 * Call `_restoreStateOfDocumentBeforeRun` afterwards.
100 async _run() {
101 // Generate the expected pasted HTML before pasting the clipboard's
102 // content, because that may duplicate ids, hence leading to creating
103 // a wrong expectation string.
104 const expectedPastedHTML = this._createExpectedOuterHTMLOfTable();
106 if (this._selectionMode == kSelectionModeDragSelection) {
107 this._dragSelectAllCellsInRowsOfTable();
108 } else {
109 this._clickSelectAllCellsInRowsOfTable();
112 await this._copyToClipboard(expectedPastedHTML);
113 this._pasteToTargetElement();
115 const targetElement = document.getElementById(kTargetElementId);
116 is(targetElement.children.length, 1,
117 "Target element has exactly one child.");
118 is(targetElement.children[0]?.tagName, kTableTagName,
119 "Target element has a table child.");
121 // Linebreaks and whitespace after tags are irrelevant, hence stripping
122 // them.
123 is(SimpleTest.stripLinebreaksAndWhitespaceAfterTags(
124 targetElement.children[0]?.outerHTML), expectedPastedHTML,
125 "Pasted table (" + this._toString() + ") has expected outerHTML.");
128 _restoreStateOfDocumentBeforeRun() {
129 if (this._editabilityMode == kEditabilityModeDesignMode) {
130 document.designMode = "off";
131 this._setContenteditableAttributeOfTarget();
134 const targetElement = document.getElementById(kTargetElementId);
135 targetElement.innerHTML = this._innerHTMLOfTargetBeforeTestRun;
136 targetElement.getBoundingClientRect();
138 SimpleTest.info(
139 "Restored the state of the document before the test run.");
142 _toString() {
143 return "table: " + this._tableName + "; row(s): " +
144 this._rowsInTable.toString() + "; editability-mode: " +
145 this._editabilityMode + "; selection-mode: " + this._selectionMode;
148 _removeContenteditableAttributeOfTarget() {
149 const targetElement = document.getElementById(kTargetElementId);
150 SimpleTest.info("Removing target's 'contenteditable' attribute.");
151 targetElement.removeAttribute("contenteditable");
154 _setContenteditableAttributeOfTarget() {
155 const targetElement = document.getElementById(kTargetElementId);
156 SimpleTest.info("Setting 'contenteditable' attribute of target.");
157 targetElement.setAttribute("contenteditable", "");
160 _getOuterHTMLAndStripLinebreaksAndWhitespaceAfterTags(aElementId) {
161 const outerHTML = document.getElementById(aElementId).outerHTML;
162 return SimpleTest.stripLinebreaksAndWhitespaceAfterTags(outerHTML);
165 _createExpectedOuterHTMLOfTable() {
166 const rowsInTableHead = FilterRowsWithParentTag(this._tableName,
167 this._rowsInTable, kTheadTagName);
169 const rowsInTableBody = FilterRowsWithParentTag(this._tableName,
170 this._rowsInTable, kTbodyTagName);
172 const rowsInTableFoot = FilterRowsWithParentTag(this._tableName,
173 this._rowsInTable, kTfootTagName);
175 let expectedTableOuterHTML = '\
176 <table>';
178 if (rowsInTableHead.length) {
179 expectedTableOuterHTML += '\
180 <thead>';
181 rowsInTableHead.forEach(rowName =>
182 expectedTableOuterHTML +=
183 this._getOuterHTMLAndStripLinebreaksAndWhitespaceAfterTags(
184 this._tableName + rowName));
185 expectedTableOuterHTML +='\
186 </thead>';
189 if (rowsInTableBody.length) {
190 expectedTableOuterHTML += '\
191 <tbody>';
193 rowsInTableBody.forEach(rowName =>
194 expectedTableOuterHTML +=
195 this._getOuterHTMLAndStripLinebreaksAndWhitespaceAfterTags(
196 this._tableName + rowName));
198 expectedTableOuterHTML +='\
199 </tbody>';
202 if (rowsInTableFoot.length) {
203 expectedTableOuterHTML += '\
204 <tfoot>';
205 rowsInTableFoot.forEach(rowName =>
206 expectedTableOuterHTML +=
207 this._getOuterHTMLAndStripLinebreaksAndWhitespaceAfterTags(this._tableName
208 + rowName));
209 expectedTableOuterHTML += '\
210 </tfoot>';
213 expectedTableOuterHTML += '\
214 </table>';
216 return expectedTableOuterHTML;
219 _clickSelectAllCellsInRowsOfTable() {
220 function synthesizeAccelKeyAndClickAt(aElementId) {
221 const element = document.getElementById(aElementId);
222 synthesizeMouseAtCenter(element, { accelKey: true });
225 this._rowsInTable.forEach(rowName => kColumns.forEach(column =>
226 synthesizeAccelKeyAndClickAt(this._tableName + rowName + column)));
229 _dragSelectAllCellsInRowsOfTable() {
230 const firstColumnOfFirstRow = document.getElementById(this._tableName +
231 this._rowsInTable[0] + kColumns[0]);
232 const lastColumnOfLastRow = document.getElementById(this._tableName +
233 this._rowsInTable.slice(-1)[0] + kColumns.slice(-1)[0]);
235 synthesizeMouse(firstColumnOfFirstRow, 0 /* aOffsetX */,
236 0 /* aOffsetY */, { type: "mousedown" } /* aEvent */);
238 const rectOfLastColumnOfLastRow =
239 lastColumnOfLastRow.getBoundingClientRect();
241 synthesizeMouse(lastColumnOfLastRow, rectOfLastColumnOfLastRow.width
242 /* aOffsetX */, rectOfLastColumnOfLastRow.height /* aOffsetY */,
243 { type: "mousemove" } /* aEvent */);
245 synthesizeMouse(lastColumnOfLastRow, rectOfLastColumnOfLastRow.width
246 /* aOffsetX */, rectOfLastColumnOfLastRow.height /* aOffsetY */,
247 { type: "mouseup" } /* aEvent */);
251 * @return a promise.
253 async _copyToClipboard(aExpectedPastedHTML) {
254 const flavor = "text/html";
256 const expectedPastedHTML = (() => {
257 if (navigator.platform.includes(kPlatformWindows)) {
258 // TODO: ideally, this should be factored out, see bug 1669963.
260 // Windows wraps the pasted HTML, see
261 // https://searchfox.org/mozilla-central/rev/8f7b017a31326515cb467e69eef1f6c965b4f00e/widget/windows/nsDataObj.cpp#1798-1805,1839-1840,1842.
262 return kTextHtmlPrefixClipboardDataWindows +
263 aExpectedPastedHTML + kTextHtmlSuffixClipboardDataWindows;
265 return aExpectedPastedHTML;
266 })();
268 function validatorFn(aData) {
269 // The data's format doesn't specify whether there should be line
270 // breaks or whitspace between tags. Hence, remove them.
271 if (SimpleTest.stripLinebreaksAndWhitespaceAfterTags(aData) ==
272 SimpleTest.stripLinebreaksAndWhitespaceAfterTags(expectedPastedHTML)) {
273 return true;
275 info(`Waiting clipboard data: expected:\n"${
276 SimpleTest.stripLinebreaksAndWhitespaceAfterTags(expectedPastedHTML)
277 }"\n, but got:\n"${
278 SimpleTest.stripLinebreaksAndWhitespaceAfterTags(aData)
279 }"`);
280 return false;
283 return SimpleTest.promiseClipboardChange(validatorFn,
284 () => synthesizeKey("c", { accelKey: true } /* aEvent*/), flavor);
287 _pasteToTargetElement() {
288 const editingHost = (this._editabilityMode ==
289 kEditabilityModeContenteditable) ?
290 document.getElementById(kTargetElementId) :
291 document;
293 let inputEvent;
294 function handleInputEvent(aEvent) {
295 if (aEvent.inputType == kInputEventInputTypeInsertFromPaste) {
296 editingHost.removeEventListener(kInputEventType, handleInputEvent);
297 SimpleTest.info(
298 'Listened to an "' + kInputEventInputTypeInsertFromPaste + '" "'
299 + kInputEventType + ' event.');
300 inputEvent = aEvent;
303 editingHost.addEventListener(kInputEventType, handleInputEvent);
305 const targetElement = document.getElementById(kTargetElementId);
306 synthesizeMouseAtCenter(targetElement, {});
307 synthesizeKey("v", { accelKey: true } /* aEvent */);
310 inputEvent != undefined,
311 `An ${kInputEventType} whose "inputType" is ${
312 kInputEventInputTypeInsertFromPaste
313 } should've been fired on ${editingHost.localName}`
318 function ContainsRowWithParentTag(aTableName, aRowsInTable, aTagName) {
319 return !!FilterRowsWithParentTag(aTableName, aRowsInTable,
320 aTagName).length;
323 function DoesContainRowInTheadAndTbody(aTableName, aRowsInTable) {
324 return ContainsRowWithParentTag(aTableName, aRowsInTable, kTheadTagName) &&
325 ContainsRowWithParentTag(aTableName, aRowsInTable, kTbodyTagName);
328 function DoesContainRowInTbodyAndTfoot(aTableName, aRowsInTable) {
329 return ContainsRowWithParentTag(aTableName, aRowsInTable, kTbodyTagName)
330 && ContainsRowWithParentTag(aTableName, aRowsInTable, kTfootTagName);
333 async function runTests() {
334 const kClickSelectionTests = {
335 selectionMode : kSelectionModeClickSelection,
336 tablesToTest : ["t1", "t2", "t3", "t4", "t5"],
337 rowsToSelect : [
338 ["r1", "r2", "r3", "r4"],
339 ["r1"],
340 ["r2", "r3"],
341 ["r1", "r3"],
342 ["r3", "r4"],
343 ["r4"],
347 const kDragSelectionTests = {
348 selectionMode : kSelectionModeDragSelection,
349 tablesToTest : ["t1", "t2", "t3", "t4", "t5"],
350 // Only consecutive rows when drag-selecting.
351 rowsToSelect : [
352 ["r1", "r2", "r3", "r4"],
353 ["r1"],
354 ["r2", "r3"],
355 ["r3", "r4"],
356 ["r4"],
360 const kTestGroups = [kClickSelectionTests, kDragSelectionTests];
362 const kEditabilityModes = [
363 kEditabilityModeContenteditable,
364 kEditabilityModeDesignMode,
367 for (const editabilityMode of kEditabilityModes) {
368 for (const testGroup of kTestGroups) {
369 for (const tableName of testGroup.tablesToTest) {
370 for (const rowsToSelect of testGroup.rowsToSelect) {
371 if (DoesContainRowInTheadAndTbody(tableName, rowsToSelect) ||
372 DoesContainRowInTbodyAndTfoot(tableName, rowsToSelect)) {
373 todo(false,
374 'Rows to select (' + rowsToSelect.toString() + ') contains ' +
375 ' row in <tbody> and <thead> or <tfoot> of table "' +
376 tableName + '", see bug 1667786.');
377 continue;
380 const test = new Test(tableName, rowsToSelect, editabilityMode,
381 testGroup.selectionMode);
382 try {
383 await test._run();
384 } catch (ex) {
385 ok(false, `Aborting the following tests due to unexpected error: ${ex.message}`);
386 SimpleTest.finish();
387 return;
389 test._restoreStateOfDocumentBeforeRun();
395 SimpleTest.finish();
398 function onLoad() {
399 SimpleTest.waitForExplicitFinish();
400 SimpleTest.waitForFocus(runTests);
402 </script>
403 </head>
404 <body onload="onLoad()">
405 <p id="display"></p>
406 <h4>Test for <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1639972">bug 1639972</a></h4>
407 <div id="content">
408 <div class="tableContainer">Table with <code>tbody</code> and <code>td</code>:
409 <table>
410 <tbody>
411 <tr id="t1r1">
412 <td id="t1r1c1">r1c1</td>
413 <td id="t1r1c2">r1c2</td>
414 <td id="t1r1c3">r1c3</td>
415 </tr>
416 <tr id="t1r2">
417 <td id="t1r2c1">r2c1</td>
418 <td id="t1r2c2">r2c2</td>
419 <td id="t1r2c3">r2c3</td>
420 </tr>
421 <tr id="t1r3">
422 <td id="t1r3c1">r3c1</td>
423 <td id="t1r3c2">r3c2</td>
424 <td id="t1r3c3">r3c3</td>
425 </tr>
426 <tr id="t1r4">
427 <td id="t1r4c1">r4c1</td>
428 <td id="t1r4c2">r4c2</td>
429 <td id="t1r4c3">r4c3</td>
430 </tr>
431 </tbody>
432 </table>
433 </div>
435 <div class="tableContainer">Table with <code>tbody</code>, <code>td</code> and <code>th</code>:
436 <table>
437 <tbody>
438 <tr id="t2r1">
439 <th id="t2r1c1">r1c1</th>
440 <th id="t2r1c2">r1c2</th>
441 <th id="t2r1c3">r1c3</th>
442 </tr>
443 <tr id="t2r2">
444 <td id="t2r2c1">r2c1</td>
445 <td id="t2r2c2">r2c2</td>
446 <td id="t2r2c3">r2c3</td>
447 </tr>
448 <tr id="t2r3">
449 <td id="t2r3c1">r3c1</td>
450 <td id="t2r3c2">r3c2</td>
451 <td id="t2r3c3">r3c3</td>
452 </tr>
453 <tr id="t2r4">
454 <td id="t2r4c1">r4c1</td>
455 <td id="t2r4c2">r4c2</td>
456 <td id="t2r4c3">r4c3</td>
457 </tr>
458 </tbody>
459 </table>
460 </div>
462 <div class="tableContainer">Table with <code>thead</code>, <code>tbody</code>, <code>td</code>:
463 <table>
464 <thead>
465 <tr id="t3r1">
466 <td id="t3r1c1">r1c1</td>
467 <td id="t3r1c2">r1c2</td>
468 <td id="t3r1c3">r1c3</td>
469 </tr>
470 </thead>
471 <tbody>
472 <tr id="t3r2">
473 <td id="t3r2c1">r2c1</td>
474 <td id="t3r2c2">r2c2</td>
475 <td id="t3r2c3">r2c3</td>
476 </tr>
477 <tr id="t3r3">
478 <td id="t3r3c1">r3c1</td>
479 <td id="t3r3c2">r3c2</td>
480 <td id="t3r3c3">r3c3</td>
481 </tr>
482 <tr id="t3r4">
483 <td id="t3r4c1">r4c1</td>
484 <td id="t3r4c2">r4c2</td>
485 <td id="t3r4c3">r4c3</td>
486 </tr>
487 </tbody>
488 </table>
489 </div>
491 <div class="tableContainer">Table with <code>thead</code>, <code>tbody</code>, <code>td</code> and <code>th</code>:
492 <table>
493 <thead>
494 <tr id="t4r1">
495 <th id="t4r1c1">r1c1</th>
496 <th id="t4r1c2">r1c2</th>
497 <th id="t4r1c3">r1c3</th>
498 </tr>
499 </thead>
500 <tbody>
501 <tr id="t4r2">
502 <td id="t4r2c1">r2c1</td>
503 <td id="t4r2c2">r2c2</td>
504 <td id="t4r2c3">r2c3</td>
505 </tr>
506 <tr id="t4r3">
507 <td id="t4r3c1">r3c1</td>
508 <td id="t4r3c2">r3c2</td>
509 <td id="t4r3c3">r3c3</td>
510 </tr>
511 <tr id="t4r4">
512 <td id="t4r4c1">r4c1</td>
513 <td id="t4r4c2">r4c2</td>
514 <td id="t4r4c3">r4c3</td>
515 </tr>
516 </tbody>
517 </table>
518 </div>
519 <div class="tableContainer">Table with <code>thead</code>,
520 <code>tbody</code>, <code>tfoot</code>, and <code>td</code>:
521 <table>
522 <thead>
523 <tr id="t5r1">
524 <td id="t5r1c1">r1c1</td>
525 <td id="t5r1c2">r1c2</td>
526 <td id="t5r1c3">r1c3</td>
527 </tr>
528 </thead>
529 <tbody>
530 <tr id="t5r2">
531 <td id="t5r2c1">r2c1</td>
532 <td id="t5r2c2">r2c2</td>
533 <td id="t5r2c3">r2c3</td>
534 </tr>
535 <tr id="t5r3">
536 <td id="t5r3c1">r3c1</td>
537 <td id="t5r3c2">r3c2</td>
538 <td id="t5r3c3">r3c3</td>
539 </tr>
540 </tbody>
541 <tfoot>
542 <tr id="t5r4">
543 <td id="t5r4c1">r4c1</td>
544 <td id="t5r4c2">r4c2</td>
545 <td id="t5r4c3">r4c3</td>
546 </tr>
547 </tfoot>
548 </table>
549 </div>
550 <p>Target for pasting:
551 <div id="targetElement" contenteditable><!-- Some content so that it can be clicked on. -->X</div>
552 </p>
553 </div>
554 </html>