Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(heading-order): Crash on page with iframes but no headings #2965

Merged
merged 7 commits into from
Jun 2, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore: refactor
  • Loading branch information
WilcoFiers committed Jun 2, 2021
commit 1d508c8275e8de3ccf9b08e6ae7a15da4310dbb1
128 changes: 49 additions & 79 deletions lib/checks/navigation/heading-order-after.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

/**
Expand All @@ -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 };
}

/**
Expand All @@ -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);
});
}

Expand Down