function test(bSelect, bRowHeader) {
				// Prepare selection states. Set the selection states of the first and last row equal to the selection state of the starting row
				// to see if already correctly set selection states are preserved.
				that.oTable.clearSelection();
				if (bSelect) {
					that.oTable.addSelectionInterval(0, 0);
					that.oTable.addSelectionInterval(iStartIndex, iStartIndex);
					that.oTable.addSelectionInterval(iNumberOfRows - 1, iNumberOfRows - 1);
				} else {
					that.oTable.selectAll();
					that.oTable.removeSelectionInterval(0, 0);
					that.oTable.removeSelectionInterval(iStartIndex, iStartIndex);
					that.oTable.removeSelectionInterval(iNumberOfRows - 1, iNumberOfRows - 1);
				}
				that.oTable.setFirstVisibleRow(iStartIndex);

				/*eslint-disable no-loop-func*/
				return that.oTable.qunit.whenRenderingFinished().then(function() {
					let oElem = that.getCellOrRowHeader(bRowHeader, 0, 0, true);

					oElem.focus();

					let pSequence = Promise.resolve().then(function() {
						that.assertSelection(assert, iStartIndex, bSelect);
						qutils.triggerKeydown(oElem, Key.SHIFT); // Start selection mode.
					});

					// Move up to the first row. All rows above the starting row should get (de)selected.
					for (i = iStartIndex - 1; i >= 0; i--) {
						(function() {
							let iIndex = i;
							const oTarget = oElem;

							pSequence = pSequence.then(function() {
								qutils.triggerKeydown(oTarget, Key.Arrow.UP, true);
							}).then(that.oTable.qunit.whenRenderingFinished).then(function() {
								const mRowCounts = that.oTable._getRowCounts();

								if (iIndex >= mRowCounts.fixedTop && i < iNumberOfRows - mRowCounts.count + mRowCounts.fixedTop + 1) {
									iIndex = mRowCounts.fixedTop;
								} else if (iIndex >= iNumberOfRows - mRowCounts.count + mRowCounts.fixedTop + 1) {
									iIndex = iIndex - (iNumberOfRows - mRowCounts.count);
								}

								oElem = that.getCellOrRowHeader(bRowHeader, iIndex, 0);
								that.assertSelection(assert, that.oTable.getRows()[iIndex].getIndex(), bSelect);
							});
						}());
					}

					pSequence = pSequence.then(function() {
						qutils.triggerKeydown(oElem, Key.Arrow.UP, true);
						that.assertSelection(assert, 0, bSelect);
					});

					// Move down to the starting row. When moving back down the rows always get deselected.
					for (i = 1; i <= iStartIndex; i++) {
						(function() {
							const mRowCounts = that.oTable._getRowCounts();
							let iIndex = i;

							pSequence = pSequence.then(function() {
								qutils.triggerKeydown(oElem, Key.Arrow.DOWN, true);
							}).then(that.oTable.qunit.whenRenderingFinished).then(function() {
								if (iIndex >= iVisibleRowCount - mRowCounts.fixedBottom && iIndex < iNumberOfRows - mRowCounts.fixedBottom) {
									iIndex = iVisibleRowCount - mRowCounts.fixedBottom - 1;
								} else if (iIndex >= iNumberOfRows - mRowCounts.fixedBottom) {
									iIndex = iIndex - (iNumberOfRows - iVisibleRowCount);
								}
								oElem = that.getCellOrRowHeader(bRowHeader, iIndex, 0);

								that.assertSelection(assert, that.oTable.getRows()[iIndex - 1].getIndex(), false);
							});
						}());
					}

					pSequence = pSequence.then(function() {
						that.assertSelection(assert, iStartIndex, bSelect); // Selection state of the starting row never gets changed.
					});

					// Move down to the last row. All rows beneath the starting row should get (de)selected.
					for (i = iStartIndex + 1; i < iNumberOfRows; i++) {
						(function() {
							const mRowCounts = that.oTable._getRowCounts();
							let iIndex = i;

							pSequence = pSequence.then(function() {
								qutils.triggerKeydown(oElem, Key.Arrow.DOWN, true);
							}).then(that.oTable.qunit.whenRenderingFinished).then(function() {
								if (iIndex >= iVisibleRowCount - mRowCounts.fixedBottom && iIndex < iNumberOfRows - mRowCounts.fixedBottom) {
									iIndex = iVisibleRowCount - mRowCounts.fixedBottom - 1;
								} else if (iIndex >= iNumberOfRows - mRowCounts.fixedBottom) {
									iIndex = iIndex - (iNumberOfRows - iVisibleRowCount);
								}
								oElem = that.getCellOrRowHeader(bRowHeader, iIndex, 0);

								that.assertSelection(assert, that.oTable.getRows()[iIndex].getIndex(), bSelect);
							});
						}());
					}

					// Move up to the starting row. When moving back up the rows always get deselected
					for (i = iNumberOfRows - 2; i >= iStartIndex; i--) {
						(function() {
							let iIndex = i;

							pSequence = pSequence.then(function() {
								qutils.triggerKeydown(oElem, Key.Arrow.UP, true);
							}).then(that.oTable.qunit.whenRenderingFinished).then(function() {
								const mRowCounts = that.oTable._getRowCounts();

								if (iIndex >= mRowCounts.fixedTop && iIndex < iNumberOfRows - mRowCounts.count + mRowCounts.fixedTop + 1) {
									iIndex = mRowCounts.fixedTop;
								} else if (iIndex >= iNumberOfRows - mRowCounts.count + mRowCounts.fixedTop + 1) {
									iIndex = iIndex - (iNumberOfRows - mRowCounts.count);
								}

								oElem = that.getCellOrRowHeader(bRowHeader, iIndex, 0);

								that.assertSelection(assert, that.oTable.getRows()[iIndex + 1].getIndex(), false);
							});
						}());
					}

					pSequence = pSequence.then(function() {
						that.assertSelection(assert, iStartIndex, bSelect); // Selection state of the starting row never gets changed.
					});

					return pSequence;
				}).then(function() {
					/* Cancellation of the row selection mode. */

					// Prepare selection states.
					if (bSelect) {
						that.oTable.clearSelection();
						that.oTable.addSelectionInterval(iStartIndex, iStartIndex);
					} else {
						that.oTable.selectAll();
						that.oTable.removeSelectionInterval(iStartIndex, iStartIndex);
					}
					that.oTable.setFirstVisibleRow(iStartIndex - (iVisibleRowCount - 1));
				}).then(that.oTable.qunit.whenRenderingFinished).then(function() {
					let oElem = that.getCellOrRowHeader(bRowHeader, iVisibleRowCount - 1, 0, true);
					let pSequence = Promise.resolve();

					// Move down to the last row. All rows beneath the starting row should get (de)selected.
					for (i = iStartIndex + 1; i < iNumberOfRows; i++) {
						(function() {
							const mRowCounts = that.oTable._getRowCounts();
							let iIndex = i;

							pSequence = pSequence.then(function() {
								qutils.triggerKeydown(oElem, Key.Arrow.DOWN, true);
							}).then(that.oTable.qunit.whenRenderingFinished).then(function() {
								if (iIndex >= iVisibleRowCount - mRowCounts.fixedBottom && iIndex < iNumberOfRows - mRowCounts.fixedBottom) {
									iIndex = iVisibleRowCount - mRowCounts.fixedBottom - 1;
								} else if (iIndex >= iNumberOfRows - mRowCounts.fixedBottom) {
									iIndex = iIndex - (iNumberOfRows - iVisibleRowCount);
								}
								oElem = that.getCellOrRowHeader(bRowHeader, iIndex, 0);

								that.assertSelection(assert, that.oTable.getRows()[iIndex].getIndex(), bSelect);
							});
						}());
					}

					pSequence = pSequence.then(function() {
						qutils.triggerKeyup(oElem, Key.SHIFT); // End selection mode.
					});

					// Move up to the starting row. Selection states should not change because selection mode was canceled.
					for (i = iNumberOfRows - 2; i >= iStartIndex; i--) {
						(function() {
							let iIndex = i;

							pSequence = pSequence.then(function() {
								qutils.triggerKeydown(oElem, Key.Arrow.UP);
							}).then(that.oTable.qunit.whenRenderingFinished).then(function() {
								const mRowCounts = that.oTable._getRowCounts();

								if (iIndex >= mRowCounts.fixedTop && iIndex < iNumberOfRows - mRowCounts.count
									+ mRowCounts.fixedTop + 1) {
									iIndex = mRowCounts.fixedTop;
								} else if (iIndex >= iNumberOfRows - mRowCounts.count + mRowCounts.fixedTop + 1) {
									iIndex = iIndex - (iNumberOfRows - mRowCounts.count);
								}

								oElem = that.getCellOrRowHeader(bRowHeader, iIndex, 0);

								that.assertSelection(assert, that.oTable.getRows()[iIndex + 1].getIndex(), bSelect);
							});
						}());
					}

					pSequence = pSequence.then(function() {
						that.assertSelection(assert, iStartIndex, bSelect); // Selection state of the starting row never gets changed.
						qutils.triggerKeydown(oElem, Key.SHIFT); // Start selection mode.
					});

					// Move up to the first row. All rows above the starting row should get (de)selected.
					for (i = iStartIndex - 1; i >= 0; i--) {
						(function() {
							let iIndex = i;

							pSequence = pSequence.then(function() {
								qutils.triggerKeydown(oElem, Key.Arrow.UP, true);
							}).then(that.oTable.qunit.whenRenderingFinished).then(function() {
								const mRowCounts = that.oTable._getRowCounts();

								if (iIndex >= mRowCounts.fixedTop && iIndex < iNumberOfRows - mRowCounts.count + mRowCounts.fixedTop + 1) {
									iIndex = mRowCounts.fixedTop;
								} else if (iIndex >= iNumberOfRows - mRowCounts.count + mRowCounts.fixedTop + 1) {
									iIndex = iIndex - (iNumberOfRows - mRowCounts.count);
								}

								oElem = that.getCellOrRowHeader(bRowHeader, iIndex, 0);
								that.assertSelection(assert, that.oTable.getRows()[iIndex].getIndex(), bSelect);
							});
						}());
					}

					pSequence = pSequence.then(function() {
						qutils.triggerKeyup(oElem, Key.SHIFT); // End selection mode.
					});

					// Move down to the starting row. Selection states should not change because selection mode was canceled.
					for (i = 1; i <= iStartIndex; i++) {
						(function() {
							const mRowCounts = that.oTable._getRowCounts();
							let iIndex = i;

							pSequence = pSequence.then(function() {
								qutils.triggerKeydown(oElem, Key.Arrow.DOWN);
							}).then(that.oTable.qunit.whenRenderingFinished).then(function() {
								if (iIndex >= iVisibleRowCount - mRowCounts.fixedTop && iIndex < iNumberOfRows - mRowCounts.fixedTop) {
									iIndex = iVisibleRowCount - mRowCounts.fixedTop - 1;
								} else if (iIndex >= iNumberOfRows - mRowCounts.fixedTop) {
									iIndex = iIndex - (iNumberOfRows - iVisibleRowCount);
								}
								oElem = that.getCellOrRowHeader(bRowHeader, iIndex, 0);

								that.assertSelection(assert, that.oTable.getRows()[iIndex - 1].getIndex(), bSelect);
							});
						}());
					}

					return pSequence;
				});
				/*eslint-enable no-loop-func*/
			}
A test for range selection and deselection.
Start from the middle of the table -> Move up to the top -> Move down to the bottom -> Move up to the starting row.

@param {Object} assert QUnit assert object.
@returns {Promise} A promise that resolves after the test completes.
@private