function checkAggregationCache4Hierarchy(sTitle, assert, oListBinding) {
		const aParentByLevel = [];
		const bUnifiedCache = oListBinding.oCache.bUnifiedCache;

		function checkCache(oCache) {
			for (let i = 0, n = oCache.aElements.$created; i < n; i += 1) {
				const oElement = oCache.aElements[i];
				if (oElement) {
					strictEqual(_Helper.hasPrivateAnnotation(oElement, "transientPredicate"), true,
						"created", oElement);
				}
			}
			for (let i = oCache.aElements.$created, n = oCache.aElements.length; i < n; i += 1) {
				const oElement = oCache.aElements[i];
				if (oElement) {
					strictEqual(_Helper.hasPrivateAnnotation(oElement, "transientPredicate"), false,
						"not created", oElement);
				}
			}
			strictEqual(oCache.aElements.$count === oCache.iLimit + oCache.iActiveElements, true,
				`${oCache.aElements.$count} === ${oCache.iLimit} + ${oCache.iActiveElements}`);
			for (const sPredicate in oCache.aElements.$byPredicate) {
				const oElement = oCache.aElements.$byPredicate[sPredicate];
				strictEqual(oCache.aElements.includes(oElement), true,
					`$byPredicate[${sPredicate}] in aElements`, oElement);
			}
		}

		function isKeepAlive(sPredicate) {
			return oListBinding.getAllCurrentContexts()
				.filter((oContext) => oContext.isEffectivelyKeptAlive())
				.some((oContext) => oContext.getPath().endsWith(sPredicate));
		}

		function strictEqual(vActual, vExpected, sMyTitle, oElement) {
			strictEqualSilent(assert, vActual, vExpected, sTitle, sMyTitle, oElement);
		}

		function visitElements(aElements, bSkipByPredicate = false, iLevelOffset = 0,
				iRankOffset = 0) {
			aElements.forEach((oElement) => {
				// Note: "@$ui5.node.level" is outdated after #move!
				const iLevel = oElement["@$ui5.node.level"] + iLevelOffset;

				// every "created" element needs to know its context (if not using createInPlace!),
				// no others must do so
				strictEqual(oElement["@$ui5.context.isTransient"] !== undefined
						&& !oListBinding.getAggregation().createInPlace,
					 _Helper.hasPrivateAnnotation(oElement, "context"),
					`"context" @ level ${iLevel}`, oElement);
				if (_Helper.hasPrivateAnnotation(oElement, "context")) {
					strictEqual(oElement["@$ui5.context.isTransient"],
						_Helper.getPrivateAnnotation(oElement, "context").isTransient(),
						`"@$ui5.context.isTransient" @ level ${iLevel}`, oElement);
				}
				strictEqual(oListBinding.oCache.oTreeState.isOutOfPlace(
						_Helper.getPrivateAnnotation(oElement, "predicate")),
					oElement["@$ui5.context.isTransient"] === false, "OOP = created persisted",
					oElement);

				if (oElement["@$ui5.node.isExpanded"] === undefined) {
					strictEqual(_Helper.getPrivateAnnotation(oElement, "descendants") === undefined,
						true, "no descendants for a leaf", oElement);
				}

				let iRank = _Helper.getPrivateAnnotation(oElement, "rank");
				const oParent = aParentByLevel[iLevel];
				if (oParent === aParentByLevel[0] && iRank !== undefined) {
					iRank += iRankOffset;
				}
				const bPlaceholder = _Helper.hasPrivateAnnotation(oElement, "placeholder");
				if (oParent) {
					strictEqual(_Helper.getPrivateAnnotation(oElement, "parent"), oParent,
						`"parent" @ level ${iLevel}`, oElement);
					if (_Helper.hasPrivateAnnotation(oElement, "transientPredicate")) {
						strictEqual(iRank, undefined,
							`created persisted @ level ${iLevel}`, oElement);
					} else if (bPlaceholder) {
						strictEqual(oParent.aElements.indexOf(oElement), -1,
							`placeholder @ level ${iLevel}`, oElement);
					} else if (iRank !== undefined) {
						strictEqual(iRank,
							oParent.aElements.indexOf(oElement) - oParent.aElements.$created,
							`$skip index @ level ${iLevel}`, oElement);
					}
				} else {
					assert.ok(false, `no known parent for level ${iLevel}`);
				}

				if (!bPlaceholder && !bSkipByPredicate) {
					const checkByPredicate = function (sKey) {
						const sPredicate = _Helper.getPrivateAnnotation(oElement, sKey);
						if (sPredicate === undefined) {
							return;
						}
						strictEqual(oListBinding.oCache.aElements.$byPredicate[sPredicate],
							oElement, `${sPredicate} in $byPredicate`, oElement);
					};

					checkByPredicate("predicate");
					checkByPredicate("transientPredicate");
				}

				const oCache = _Helper.getPrivateAnnotation(oElement, "cache");
				if (oCache) {
					strictEqual(bUnifiedCache, false,
						"must not have a group level cache", oElement);
					checkCache(oCache);
					aParentByLevel[iLevel + 1] = oCache;
				}

				const aSpliced = _Helper.getPrivateAnnotation(oElement, "spliced");
				if (aSpliced) {
					visitElements(aSpliced, true, iLevel + 1 - aSpliced[0]["@$ui5.node.level"],
						iRank - aSpliced.$rank);
				}
			});
		}

		const aElements = oListBinding.oCache.aElements;
		strictEqual(aElements.length, aElements.$count, "$count");
		for (const sPredicate in aElements.$byPredicate) {
			const oElement = aElements.$byPredicate[sPredicate];
			strictEqual(oElement["@$ui5.context.isDeleted"] || aElements.includes(oElement)
					|| isKeepAlive(sPredicate),
				true, `$byPredicate[${sPredicate}] in aElements`, oElement);
			strictEqual(_Helper.getPrivateAnnotation(oElement, "predicate") === sPredicate
					|| _Helper.getPrivateAnnotation(oElement, "transientPredicate") === sPredicate,
				true, `unknown predicate ${sPredicate}`, oElement);
		}

		let iExpandTo = oListBinding.getAggregation().expandTo || 1;
		if (iExpandTo >= Number.MAX_SAFE_INTEGER || bUnifiedCache) {
			iExpandTo = 99; // avoid "Invalid array length" :-)
		}
		for (let i = 0; i <= iExpandTo; i += 1) {
			// Note: level 0 or 1 is used for initial placeholders of 1st level cache!
			aParentByLevel[i] = oListBinding.oCache.oFirstLevel;
		}
		checkCache(oListBinding.oCache.oFirstLevel);
		visitElements(aElements);
		if (oListBinding.getAggregation().createInPlace) {
			strictEqual(oListBinding.oCache.oTreeState.getOutOfPlacePredicates().length, 0,
				"no OOP w/ createInPlace");
		}

		if (iExpandTo > 1) {
			const aElements0 = oListBinding.oCache.oFirstLevel.aElements;
			if (!aElements0.$created && aElements0.$count === aElements0.length) {
				aElements0.forEach((oElement, i) => {
					if (oElement?.["@$ui5.node.isExpanded"]) {
						const iLevel = oElement["@$ui5.node.level"];
						let iDescendants = 0;
						for (let j = i + 1; j < aElements0.length; j += 1) {
							if (!aElements0[j]) {
								return; // cannot count "descendants" for this oElement
							}
							if (iLevel >= aElements0[j]["@$ui5.node.level"]) {
								break; // end of "descendants"
							}
							iDescendants += 1;
						}
						strictEqual(_Helper.getPrivateAnnotation(oElement, "descendants", 0),
							iDescendants, "descendants", oElement);
					}
				});
			} // else: cannot count "descendants" this way
		}
	}
Checks that the given list binding's _AggregationCache has a consistent state.

@param {string} sTitle - A test title
@param {object} assert - The QUnit assert object
@param {sap.ui.model.odata.v4.ODataListBinding} oListBinding - A list binding