Skip to content

Commit

Permalink
update test cases and rule #1674
Browse files Browse the repository at this point in the history
  • Loading branch information
shunguoy committed Oct 31, 2023
1 parent 72f38be commit c29fff8
Show file tree
Hide file tree
Showing 11 changed files with 995 additions and 225 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ export class RPTUtil {

/**
* a target is en element that accept a pointer action (click or touch)
*
*/
public static isTarget(element) {
if (!element) return false;
Expand Down Expand Up @@ -451,75 +452,24 @@ export class RPTUtil {
}

/**
* an "inline" CSS display property tells the element to fit itself on the same line. An 'inline' element's width and height are ignored.
* some element has default inline property, such as <span>, <a>
* most formatting elements inherent inline property, such as <em>, <strong>, <i>, <small>
* other inline elements: <abbr> <acronym> <b> <bdo> <big> <br> <cite> <code> <dfn> <em> <i> <input> <kbd> <label>
* <map> <object> <output> <q> <samp> <script> <select> <small> <span> <strong> <sub> <sup> <textarea> <time> <tt> <var>
* an "inline-block" element still place element in the same line without breaking the line, but the element's width and height are applied.
* inline-block elements: img, button, select, meter, progress, marguee, also in Chrome: textarea, input
* A block-level element always starts on a new line, and the browsers automatically add some space (a margin) before and after the element.
* block-level elements: <address> <article> <aside> <blockquote> <canvas> <dd> <div> <dl> <dt> <fieldset> <figcaption> <figure> <footer> <form>
* <h1>-<h6> <header> <hr> <li> <main> <nav> <noscript> <ol> <p> <pre> <section> <table> <tfoot> <ul> <video>
*
* a target is en element that accept a pointer action (click or touch)
* a target is a browser default if it's a native widget (no user defined role) without user style
*/
public static isInline(element) {
public static isTargetBrowserDefault(element) {
if (!element) return false;

const style = getComputedStyle(element);
if (!style) return false;
const udisplay = style.getPropertyValue("display");
// inline element only
if (udisplay !== 'inline')
// user defained widget
const roles = RPTUtil.getRoles(element, false);
if (roles && roles.length > 0)
return false;

const parent = element.parentElement;
if (parent) {
const style = getComputedStyle(parent);
const display = style.getPropertyValue("display");
// an inline element is inside a block. note <body> is a block element too
if (display === 'block' || display === 'inline-block') {
let containText = false;
// one or more inline elements with text in the same line: text<target>, <target>text, <inline>+text<target>, <target><inline>+text, text<target><inline>+
let walkNode = element.nextSibling;
while (!containText && walkNode) {
// note browsers insert Text nodes to represent whitespaces.
if (walkNode.nodeType === Node.TEXT_NODE && walkNode.nodeValue && walkNode.nodeValue.trim().length > 0) {
containText = true;
} else if (walkNode.nodeType === Node.ELEMENT_NODE) {
// special case: <br> is styled 'inline' by default, but change the line
if (walkNode.nodeName.toLowerCase() === 'br') break;
const cStyle = getComputedStyle(walkNode);
const cDisplay = cStyle.getPropertyValue("display");
if (cDisplay !== 'inline') break;
}
walkNode = walkNode.nextSibling;
}
if (!containText) {
walkNode = element.previousSibling;
while (!containText && walkNode) {
// note browsers insert Text nodes to represent whitespaces.
if (walkNode.nodeType === Node.TEXT_NODE && walkNode.nodeValue && walkNode.nodeValue.trim().length > 0) {
containText = true;
} else if (walkNode.nodeType === Node.ELEMENT_NODE) {
// special case: <br> is styled 'inline' by default, but change the line
if (walkNode.nodeName.toLowerCase() === 'br') break;
const cStyle = getComputedStyle(walkNode);
const cDisplay = cStyle.getPropertyValue("display");
if (cDisplay !== 'inline') break;
}
walkNode = walkNode.previousSibling;
}
}

// one or more inline elements are in the same line with text
if (containText) return true;
// one or more inline elements are in the same inline without text
return false;
}
}
// all other cases
return false;
// no user style to space control size, including use of font
const styles = getDefinedStyles(element);
if (styles['line-height'] || styles['height'] || styles['width'] || styles['min-height'] || styles['min-width']
|| styles['font-size'] || styles['margin-top'] || styles['margin-bottom'] || styles['margin-left'] || styles['margin-right'])
return false;

return true;
}

/**
Expand All @@ -534,20 +484,21 @@ export class RPTUtil {
* block-level elements: <address> <article> <aside> <blockquote> <canvas> <dd> <div> <dl> <dt> <fieldset> <figcaption> <figure> <footer> <form>
* <h1>-<h6> <header> <hr> <li> <main> <nav> <noscript> <ol> <p> <pre> <section> <table> <tfoot> <ul> <video>
*
* return: if it's inline element and { inline: true | false, violation: null | {node} }
* return: if it's inline element and { inline: true | false, text: true | false, violation: null | {node} }
*/
public static getInlineStatus(element) {
if (!element) return null;

const style = getComputedStyle(element);
if (!style) return null;

let status = { "inline": false, "violation": null };
let status = { "inline": false, "text": false, "violation": null };
const udisplay = style.getPropertyValue("display");
// inline element only
if (udisplay !== 'inline')
return status;

status.inline = true;
const parent = element.parentElement;
if (parent) {
const mapper : DOMMapper = new DOMMapper();
Expand All @@ -565,9 +516,8 @@ export class RPTUtil {
if (!containText && walkNode.nodeType === Node.TEXT_NODE && walkNode.nodeValue && walkNode.nodeValue.trim().length > 0) {
containText = true;
} else if (walkNode.nodeType === Node.ELEMENT_NODE) {
if (status.violation === null) {
// special case: <br> is styled 'inline' by default, but change the line
if (walkNode.nodeName.toLowerCase() === 'br') break;
// special case: <br> is styled 'inline' by default, but change the line
if (status.violation === null && walkNode.nodeName.toLowerCase() !== 'br') {
const cStyle = getComputedStyle(walkNode);
const cDisplay = cStyle.getPropertyValue("display"); console.log("target id=" + element.getAttribute("id") +", node id=" + walkNode.getAttribute("id")+", bounds=" + JSON.stringify(bounds));
if (cDisplay === 'inline') {
Expand All @@ -593,9 +543,8 @@ export class RPTUtil {
if (!containText && walkNode.nodeType === Node.TEXT_NODE && walkNode.nodeValue && walkNode.nodeValue.trim().length > 0) {
containText = true;
} else if (walkNode.nodeType === Node.ELEMENT_NODE) {
if (!checked) {
// special case: <br> is styled 'inline' by default, but change the line
if (walkNode.nodeName.toLowerCase() === 'br') break;
// special case: <br> is styled 'inline' by default, but change the line
if (!checked && walkNode.nodeName.toLowerCase() !== 'br') {
const cStyle = getComputedStyle(walkNode);
const cDisplay = cStyle.getPropertyValue("display");
if (cDisplay === 'inline') {
Expand All @@ -617,9 +566,9 @@ export class RPTUtil {

// one or more inline elements are in the same line with text
if (containText) {
status.inline = true;
status.text = true;

const bnds = mapper.getBounds(parent);
/**const bnds = mapper.getBounds(parent);
// the element is the last inline element in the line, check against parent bounds
if (last) {
if (Math.round(bounds.width/2) + bnds.left+bnds.width - (bounds.left + bounds.width) < 24)
Expand All @@ -630,13 +579,17 @@ export class RPTUtil {
if (first && checked) {
if (Math.round(bounds.width/2) + bounds.left - bnds.left < 24)
status.violation = status.violation === null ? parent.nodeName.toLowerCase() : status.violation + ", " + parent.nodeName.toLowerCase();
}
}*/
}
return status;
} else {
//parent is inline element
if (!RPTUtil.isInnerTextOnlyEmpty(parent))
status.text = true;
}
}
// all other cases
return null;
return status;
}

public static tabIndexLEZero(elem) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import { eRulePolicy, eToolkitLevel } from "../api/IRule";
import { VisUtil } from "../../v2/dom/VisUtil";
import { DOMMapper } from "../../v2/dom/DOMMapper";
import { getComputedStyle } from "../util/CSSUtil";
import { getDefinedStyles, getComputedStyle } from "../util/CSSUtil";

export let target_spacing_sufficient: Rule = {
id: "target_spacing_sufficient",
Expand Down Expand Up @@ -65,22 +65,29 @@
const ruleContext = context["dom"].node as HTMLElement;
const nodeName = ruleContext.nodeName.toLocaleLowerCase();
//ignore certain elements
if (RPTUtil.getAncestor(ruleContext, ["pre", "code", "script", "meta", 'head']) !== null
if (RPTUtil.getAncestor(ruleContext, ["svg", "pre", "code", "script", "meta", 'head']) !== null
|| nodeName === "body" || nodeName === "html" )
return null;

// ignore hidden, non-target, or inline element without text in the same line
if (!VisUtil.isNodeVisible(ruleContext) || !RPTUtil.isTarget(ruleContext))
return null;

// ignore inline element: without text in the same line
const status = RPTUtil.getInlineStatus(ruleContext); console.log("node id=" + ruleContext.getAttribute("id") +", status=" + JSON.stringify(status));
if (status == null) return null;
if (status.inline) {
if (status.violation == null)
return RulePass("pass_inline");
else
// case 1: inline element is too close horizontally
return RulePass("recommendation_inline", [nodeName, status.violation]);
if (status.text) {
if (status.violation == null)
return RulePass("pass_inline");
else
// case 1: inline element is too close horizontally
return RulePass("recommendation_inline", [nodeName, status.violation]);
}
} else { console.log("target id=" + ruleContext.getAttribute('id') +", defalt=" + RPTUtil.isTargetBrowserDefault(ruleContext));
// ignore browser default
if (RPTUtil.isTargetBrowserDefault(ruleContext))
return RulePass("pass_default");
}

var doc = ruleContext.ownerDocument;
Expand All @@ -100,12 +107,12 @@
if (!elems || elems.length == 0)
return;

const bounds = context["dom"].bounds;
const mapper : DOMMapper = new DOMMapper();
const bounds = mapper.getBounds(ruleContext); //context["dom"].bounds;
if (!bounds || bounds['height'] === 0 || bounds['width'] === 0 )
return null;


const mapper : DOMMapper = new DOMMapper(); console.log("target node id=" + ruleContext.getAttribute("id") +", calculated bounds=" + JSON.stringify(mapper.getBounds(ruleContext)));
let before = true;
let minX = 24;
let minY = 24;
Expand Down Expand Up @@ -157,9 +164,9 @@
if (bounds.top <= bnds.top && bounds.left <= bnds.left && bounds.top + bounds.height >= bnds.top + bnds.height
&& bounds.left + bounds.width >= bnds.left + bnds.width) { console.log("element covered by target id=" + ruleContext.getAttribute('id') + ", elem id=" + elem.getAttribute('id') +",bounds=" +JSON.stringify(bounds) +", bnds=" + JSON.stringify(bnds));
// if the element on top
if (before ? parseInt(zindex) > parseInt(z_index): parseInt(zindex) >= parseInt(z_index)) {console.log("element on top id=" + ruleContext.getAttribute('id') + ", elem id=" + elem.getAttribute('id') +",bounds=" +JSON.stringify(bounds) +", bnds=" + JSON.stringify(bnds));
if (before ? parseInt(zindex) < parseInt(z_index): parseInt(zindex) <= parseInt(z_index)) {console.log("element on top id=" + ruleContext.getAttribute('id') + ", elem id=" + elem.getAttribute('id') +",bounds=" +JSON.stringify(bounds) +", bnds=" + JSON.stringify(bnds));
return RulePotential("potential_overlap", [nodeName, elem.nodeName.toLowerCase()]);
} else {
} else { // the target on top
if (bnds.height >= 24 && bnds.width >= 24)
return RulePass("pass_sized");
return RuleFail("violation_spacing", [nodeName, elem.nodeName.toLowerCase()]);
Expand Down Expand Up @@ -196,11 +203,11 @@
if (disY < minY) {
minY = disY;
adjacentY = elem;
}
} console.log("target id=" + ruleContext.getAttribute('id') + ", not overlap elem id=" + elem.getAttribute('id') +", minX=" + minX +", minY=" + minY +",bounds=" +JSON.stringify(bounds) +", bnds=" + JSON.stringify(bnds));
}
checked.push(elem);
}

console.log("target id=" + ruleContext.getAttribute('id') +", minX=" + minX +", minY=" + minY);
// case 5: no verlap + sufficient target size
if (bounds.height >= 24 && bounds.width >= 24) {console.log("passed_size target id=" + ruleContext.getAttribute('id'));
return RulePass("pass_sized");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,15 @@ <h1>The display Property</h1>

<h2>display: inline</h2>
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum consequat scelerisque elit sit amet consequat. Aliquam erat volutpat.
<a class='link' href="google.com">Aliquam</a>
<button class='btn' value='venenatis'></button>
gravida nisl sit amet facilisis. Nullam cursus fermentum velit sed laoreet. </div>
<button class='btn2' value='venenatis'></button>
gravida nisl sit amet facilisis. Nullam cursus fermentum velit sed laoreet. </div>
<button class='btn3' value='venenatis'></button>

<a id='a' class='link' href="google.com">Aliquam</a>
<button id='button1' class='btn' value='venenatis'></button>
gravida nisl sit amet facilisis. Nullam cursus fermentum velit sed laoreet.
</div>
<div>
<button id='button2' class='btn2' value='venenatis'></button>
gravida nisl sit amet facilisis. Nullam cursus fermentum velit sed laoreet.
<button id='button3' class='btn3' value='venenatis'></button>
</div>


<script>
Expand Down Expand Up @@ -120,7 +122,7 @@ <h2>display: inline</h2>
"PASS"
],
"path": {
"dom": "/html[1]/body[1]/button[1]",
"dom": "/html[1]/body[1]/div[2]/button[1]",
"aria": "/document[1]/button[2]"
},
"reasonId": "pass_sized",
Expand All @@ -136,11 +138,11 @@ <h2>display: inline</h2>
"PASS"
],
"path": {
"dom": "/html[1]/body[1]/button[2]",
"dom": "/html[1]/body[1]/div[2]/button[2]",
"aria": "/document[1]/button[3]"
},
"reasonId": "pass_spacing",
"message": "The target's spacing from other targets is more than minimum",
"reasonId": "pass_default",
"message": "The size of the target is determined by the user agent and is not modified by the author",
"messageArgs": [],
"apiArgs": [],
"category": "Other"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,18 @@
"ruleId": "target_spacing_sufficient",
"value": [
"INFORMATION",
"PASS"
"POTENTIAL"
],
"path": {
"dom": "/html[1]/body[1]/div[2]",
"aria": "/document[1]"
},
"reasonId": "pass_sized",
"message": "The target’s size is more than 24 CSS pixels",
"messageArgs": [],
"reasonId": "potential_overlap",
"message": "Ensure the overlapped element 'div' meets a minimum target size or has sufficient spacing from the overlapping element 'div'",
"messageArgs": [
"div",
"div"
],
"apiArgs": [],
"category": "Other"
}
Expand Down
Loading

0 comments on commit c29fff8

Please sign in to comment.