diff --git a/lib/checks/navigation/heading-order-after.js b/lib/checks/navigation/heading-order-after.js index 9959cb6e3b..e21606471f 100644 --- a/lib/checks/navigation/heading-order-after.js +++ b/lib/checks/navigation/heading-order-after.js @@ -11,23 +11,20 @@ export default function headingOrderAfter(results) { * Determine check outcome, based on the position of the result in the headingOrder */ function getHeadingOrderOutcome(result, headingOrder) { - const index = headingOrder.findIndex(heading => heading.result === result); + const index = findHeadingOrderIndex(headingOrder, result.node.ancestry) const currLevel = headingOrder[index]?.level ?? -1; const prevLevel = headingOrder[index - 1]?.level ?? -1; + // First heading always passes if (index === 0) { return true }; - // Heading not in the map if (currLevel === -1) { - return undefined; - // Heading level is skipped - } else if (currLevel - prevLevel > 1) { - return false; - } else { - return true; + return undefined; } + // Check if a heading is skipped + return (currLevel - prevLevel <= 1) } /** @@ -40,101 +37,74 @@ function getHeadingOrder(results) { results.sort(({ node: nodeA }, { node: nodeB }) => { return nodeA.ancestry.length - nodeB.ancestry.length; }); - - // Recursively push or splice result.data into headingOrder - const headingOrder = results.reduce(addResultToHeadingOrder, []); + // push or splice result.data into headingOrder + const headingOrder = results.reduce(mergeHeadingOrder, []); + // Remove all frame placeholders that was fully replaced return headingOrder.filter(heading => heading && !heading.replaced); } /** * Add the data of a heading-order result to the headingOrder map */ -function addResultToHeadingOrder(headingOrder, result) { - let frameHeadingOrder = result?.data?.headingOrder; - // Only the first selected element in the window has headingOrder info +function mergeHeadingOrder(mergedHeadingOrder, result) { + const frameHeadingOrder = result.data?.headingOrder; + const frameAncestry = shortenArray(result.node.ancestry, 1); + + // Only the first result in each frame has a headingOrder. Ignore the rest if (!frameHeadingOrder) { - setResultInHeadingOrder(headingOrder, result); - return headingOrder; + return mergedHeadingOrder; } - // Update the ancestry to include frame information - frameHeadingOrder = frameHeadingOrder.map(heading => { - return normalizeHeading(heading, result); + // Prepend node ancestry to each heading.ancestry + const normalizedHeadingOrder = frameHeadingOrder.map(heading => { + return addFrameToHeadingAncestry(heading, frameAncestry); }); - const index = getFrameIndex(headingOrder, result); + // Find if the result is from a frame previously processed + const index = getFrameIndex(mergedHeadingOrder, frameAncestry); // heading is not in a frame, stick 'm in at the end. if (index === -1) { - headingOrder.push(...frameHeadingOrder); + mergedHeadingOrder.push(...normalizedHeadingOrder); } else { - // Mark the frame for later removal - // Keep it, for nested iframes where a parent has no headings - headingOrder[index].replaced = true; - headingOrder.splice(index, 0, ...frameHeadingOrder); + // Flag the frame placeholder so it can be deleted later + mergedHeadingOrder[index].replaced = true; + mergedHeadingOrder.splice(index, 0, ...normalizedHeadingOrder); } - return headingOrder; + return mergedHeadingOrder; } /** - * Determine where the iframe results fit into the top-level - * heading order + * Determine where the iframe results fit into the top-level heading order + * + * If a frame has no headings, but it does have iframes we might not have a result. + * We can account for this by finding the closest ancestor we do know about. */ -function getFrameIndex(headingOrder, result) { - let index = -1; - const ancestry = shortenArray(result.node.ancestry, 1); - // If a frame has no headings, but it does have iframes we might - // not have a result. We can account for this by finding the closest - // ancestor we do know about. - while (ancestry.length && index === -1) { - index = headingOrder.findIndex(heading => { - return matchAncestry(heading.ancestry, ancestry); - }); - ancestry.pop(); +function getFrameIndex(headingOrder, frameAncestry) { + while (frameAncestry.length) { + const index = findHeadingOrderIndex(headingOrder, frameAncestry); + if (index !== -1) { + return index; + } + frameAncestry = shortenArray(frameAncestry, 1) } - return index; + return -1; } /** - * Find the heading based on ancestry, and set the result property + * Find the index of a heading in the headingOrder by matching ancestries */ -function setResultInHeadingOrder(headingOrder, result) { - const ancestry = result.node.ancestry; - const index = headingOrder.findIndex(heading => { +function findHeadingOrderIndex(headingOrder, ancestry) { + return headingOrder.findIndex(heading => { return matchAncestry(heading.ancestry, ancestry); }); - - if (index === -1) { - // Something went wrong, set it to incomplete - result.result = undefined; - return; - } - - headingOrder[index] = { - ...headingOrder[index], - result - }; -} - -/** - * Add all required props to the heading - */ -function normalizeHeading(heading, result) { - const ancestry = combineAncestry(result, heading); - const resultMatches = matchAncestry(result.node.ancestry, ancestry); - return { - ...heading, - result: resultMatches ? result : undefined, - ancestry - }; } /** - * Take the frame ancestry from a result, and add it to the ancestry - * of a heading. + * Prepend the frame ancestry of a node to heading.ancestry */ -function combineAncestry(result, heading) { - const frameAncestry = shortenArray(result.node.ancestry, 1); - return frameAncestry.concat(heading.ancestry); +function addFrameToHeadingAncestry(heading, frameAncestry) { + const ancestry = frameAncestry.concat(heading.ancestry); + return { ...heading, ancestry }; } /** @@ -146,13 +116,13 @@ function matchAncestry(ancestryA, ancestryB) { } return ancestryA.every((selectorA, index) => { const selectorB = ancestryB[index]; - if (Array.isArray(selectorA)) { - if (selectorA.length !== selectorB.length) { - return false; - } - return selectorA.every((str, index) => selectorB[index] === str); + if (!Array.isArray(selectorA)) { + return selectorA === selectorB; + } + if (selectorA.length !== selectorB.length) { + return false; } - return selectorA === selectorB; + return selectorA.every((str, index) => selectorB[index] === str); }); }