@guidepup/virtual-screen-reader
Advanced tools
Comparing version 0.14.0 to 0.15.0
@@ -5,17 +5,83 @@ "use strict"; | ||
const mapAttributeNameAndValueToLabel_1 = require("./mapAttributeNameAndValueToLabel"); | ||
// REF: https://www.w3.org/TR/html-aria/#docconformance-attr | ||
const isNotMatchingElement = ({ elements, node }) => elements.length && !elements.includes(node.localName); | ||
const isNotMatchingProperties = ({ node, properties }) => properties.length && | ||
!properties.some(({ key, value }) => node.getAttribute(key) === value); | ||
// REFs: | ||
// - https://www.w3.org/TR/html-aria/#docconformance-attr | ||
// - https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings | ||
const ariaToHTMLAttributeMapping = { | ||
"aria-checked": [{ name: "checked" }], | ||
"aria-disabled": [{ name: "disabled" }], | ||
"aria-autocomplete": [ | ||
{ elements: ["form"], name: "autocomplete" }, | ||
{ elements: ["input", "select", "textarea"], name: "autocomplete" }, | ||
], | ||
"aria-checked": [ | ||
{ | ||
elements: ["input"], | ||
implicitMissingValue: "false", | ||
name: "checked", | ||
properties: [ | ||
{ key: "type", value: "checkbox" }, | ||
{ key: "type", value: "radio" }, | ||
], | ||
}, | ||
{ | ||
value: "mixed", | ||
name: "indeterminate", | ||
}, | ||
], | ||
"aria-colspan": [{ elements: ["td", "th"], name: "colspan" }], | ||
"aria-controls": [ | ||
{ | ||
elements: ["input"], | ||
name: "list", | ||
}, | ||
], | ||
"aria-disabled": [ | ||
{ | ||
elements: ["button", "input", "optgroup", "option", "select", "textarea"], | ||
name: "disabled", | ||
}, | ||
{ | ||
// TODO: Form controls within a valid legend child element of a fieldset | ||
// with a disabled attribute do not become disabled. | ||
elements: ["fieldset"], | ||
name: "disabled", | ||
}, | ||
], | ||
// TODO: Set properties on the summary element. | ||
// REF: https://www.w3.org/TR/html-aam-1.0/#att-open-details | ||
// "aria-expanded": [{ elements: ["details"], name: "open" }], | ||
// Not announced, indeed it will be hidden from the accessibility tree. | ||
// "aria-hidden": [{ name: "hidden" }], | ||
"aria-placeholder": [{ name: "placeholder" }], | ||
"aria-valuemax": [{ name: "max" }], | ||
"aria-valuemin": [{ name: "min" }], | ||
"aria-invalid": [ | ||
// TODO: If the value doesn't match the pattern: aria-invalid="true"; | ||
// Otherwise, aria-invalid="false" | ||
// REF: https://www.w3.org/TR/html-aam-1.0/#att-pattern | ||
// { elements: ["input"], name: "pattern" }, | ||
// TODO: aria-invalid="spelling" or grammar | ||
// REF: https://www.w3.org/TR/html-aam-1.0/#att-spellcheck | ||
// { elements: ["input"], name: "spellcheck" }, | ||
], | ||
"aria-multiselectable": [{ elements: ["select"], name: "multiple" }], | ||
"aria-placeholder": [ | ||
{ elements: ["input", "textarea"], name: "placeholder" }, | ||
], | ||
"aria-valuemax": [ | ||
{ elements: ["input"], name: "max" }, | ||
{ elements: ["meter", "progress"], name: "max" }, | ||
], | ||
"aria-valuemin": [ | ||
{ elements: ["input"], name: "min" }, | ||
{ elements: ["meter", "progress"], name: "min" }, | ||
], | ||
"aria-valuenow": [{ elements: ["meter", "progress"], name: "value" }], | ||
"aria-readonly": [ | ||
{ name: "readonly" }, | ||
{ elements: ["input", "textarea"], name: "readonly" }, | ||
{ name: "contenteditable", negative: true }, | ||
], | ||
"aria-required": [{ name: "required" }], | ||
"aria-colspan": [{ name: "colspan" }], | ||
"aria-rowspan": [{ name: "rowspan" }], | ||
"aria-required": [ | ||
{ elements: ["input", "select", "textarea"], name: "required" }, | ||
], | ||
"aria-rowspan": [{ elements: ["td", "th"], name: "rowspan" }], | ||
"aria-selected": [{ elements: ["option"], name: "selected" }], | ||
}; | ||
@@ -27,4 +93,14 @@ const getLabelFromHtmlEquivalentAttribute = ({ attributeName, container, node, }) => { | ||
} | ||
for (const { name, negative = false } of htmlAttribute) { | ||
const attributeValue = node.getAttribute(name); | ||
for (const { elements = [], implicitMissingValue, name, negative = false, properties = [], value, } of htmlAttribute) { | ||
if (isNotMatchingElement({ elements, node })) { | ||
continue; | ||
} | ||
if (isNotMatchingProperties({ node, properties })) { | ||
continue; | ||
} | ||
const attributeValue = node.hasAttribute(name) | ||
? value ?? node.getAttribute(name) | ||
: node.hasAttribute(attributeName) | ||
? undefined | ||
: implicitMissingValue; | ||
const label = (0, mapAttributeNameAndValueToLabel_1.mapAttributeNameAndValueToLabel)({ | ||
@@ -31,0 +107,0 @@ attributeName, |
@@ -50,3 +50,3 @@ "use strict"; | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("checkbox, Label"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("checkbox, Label, not checked"); | ||
await __1.virtual.stop(); | ||
@@ -62,3 +62,3 @@ }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("radio, Label"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("radio, Label, not checked"); | ||
await __1.virtual.stop(); | ||
@@ -74,3 +74,3 @@ }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("radio, Label"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("radio, Label, not checked"); | ||
await __1.virtual.stop(); | ||
@@ -101,3 +101,3 @@ }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("listbox, second; third, orientated vertically"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("listbox, second; third, multi-selectable, orientated vertically"); | ||
await __1.virtual.stop(); | ||
@@ -127,6 +127,6 @@ }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("progressbar, Loading:, 23, max value 100"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("progressbar, Loading:, 23, max value 100, current value 23%"); | ||
await __1.virtual.stop(); | ||
}); | ||
it("should not announce the value for a progress element which has an aria-valuenow", async () => { | ||
it("should announce the value in place of aria-valuenow for a progress element", async () => { | ||
document.body.innerHTML = ` | ||
@@ -139,3 +139,3 @@ <label for="element1">Loading:</label> | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("progressbar, Loading:, max value 100, current value 24%"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("progressbar, Loading:, max value 100, current value 23%"); | ||
await __1.virtual.stop(); | ||
@@ -142,0 +142,0 @@ }); |
@@ -61,8 +61,4 @@ "use strict"; | ||
<span id="element1">Label</span> | ||
<span role="progressbar" aria-labelledby="element1" aria-valuenow="1" min="1"> | ||
<svg width="300" height="10"> | ||
<rect height="10" width="100" stroke="black" fill="red" /> | ||
<rect height="10" width="200" fill="white" /> | ||
</svg> | ||
</span> | ||
<progress aria-labelledby="element1" aria-valuenow="1" min="1"> | ||
</progress> | ||
`; | ||
@@ -78,8 +74,4 @@ await __1.virtual.start({ container: document.body }); | ||
<span id="element1">Label</span> | ||
<span role="progressbar" aria-labelledby="element1" aria-valuenow="1" max="3"> | ||
<svg width="300" height="10"> | ||
<rect height="10" width="100" stroke="black" fill="red" /> | ||
<rect height="10" width="200" fill="white" /> | ||
</svg> | ||
</span> | ||
<progress aria-labelledby="element1" aria-valuenow="1" max="3"> | ||
</progress> | ||
`; | ||
@@ -161,3 +153,3 @@ await __1.virtual.start({ container: document.body }); | ||
<span id="element1">Label</span> | ||
<span role="scrollbar" aria-labelledby="element1" aria-valuenow="1"> | ||
<span role="scrollbar" aria-controls="scrollable-target" aria-labelledby="element1" aria-valuenow="1"> | ||
<svg width="300" height="10"> | ||
@@ -168,2 +160,3 @@ <rect height="10" width="100" stroke="black" fill="red" /> | ||
</span> | ||
<span id="scrollable-target">Scrollable Region</span> | ||
`; | ||
@@ -173,3 +166,3 @@ await __1.virtual.start({ container: document.body }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 100, min value 0, current value 1%"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 100, min value 0, 1 control, current value 1%"); | ||
await __1.virtual.stop(); | ||
@@ -180,3 +173,3 @@ }); | ||
<span id="element1">Label</span> | ||
<span role="scrollbar" aria-labelledby="element1" aria-valuenow="1" aria-valuemin="1"> | ||
<span role="scrollbar" aria-controls="scrollable-target" aria-labelledby="element1" aria-valuenow="1" aria-valuemin="1"> | ||
<svg width="300" height="10"> | ||
@@ -187,2 +180,3 @@ <rect height="10" width="100" stroke="black" fill="red" /> | ||
</span> | ||
<span id="scrollable-target">Scrollable Region</span> | ||
`; | ||
@@ -192,3 +186,3 @@ await __1.virtual.start({ container: document.body }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 100, min value 1, current value 0%"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 100, min value 1, 1 control, current value 0%"); | ||
await __1.virtual.stop(); | ||
@@ -199,3 +193,3 @@ }); | ||
<span id="element1">Label</span> | ||
<span role="scrollbar" aria-labelledby="element1" aria-valuenow="1" aria-valuemax="3"> | ||
<span role="scrollbar" aria-controls="scrollable-target" aria-labelledby="element1" aria-valuenow="1" aria-valuemax="3"> | ||
<svg width="300" height="10"> | ||
@@ -206,2 +200,3 @@ <rect height="10" width="100" stroke="black" fill="red" /> | ||
</span> | ||
<span id="scrollable-target">Scrollable Region</span> | ||
`; | ||
@@ -211,41 +206,9 @@ await __1.virtual.start({ container: document.body }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 3, min value 0, current value 33.33%"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 3, min value 0, 1 control, current value 33.33%"); | ||
await __1.virtual.stop(); | ||
}); | ||
it("should announce aria-valuenow on scrollbar roles as a percentage when only min is provided", async () => { | ||
document.body.innerHTML = ` | ||
<span id="element1">Label</span> | ||
<span role="scrollbar" aria-labelledby="element1" aria-valuenow="1" min="1"> | ||
<svg width="300" height="10"> | ||
<rect height="10" width="100" stroke="black" fill="red" /> | ||
<rect height="10" width="200" fill="white" /> | ||
</svg> | ||
</span> | ||
`; | ||
await __1.virtual.start({ container: document.body }); | ||
await __1.virtual.next(); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 100, min value 1, current value 0%"); | ||
await __1.virtual.stop(); | ||
}); | ||
it("should announce aria-valuenow on scrollbar roles as a percentage when only max is provided", async () => { | ||
document.body.innerHTML = ` | ||
<span id="element1">Label</span> | ||
<span role="scrollbar" aria-labelledby="element1" aria-valuenow="1" max="3"> | ||
<svg width="300" height="10"> | ||
<rect height="10" width="100" stroke="black" fill="red" /> | ||
<rect height="10" width="200" fill="white" /> | ||
</svg> | ||
</span> | ||
`; | ||
await __1.virtual.start({ container: document.body }); | ||
await __1.virtual.next(); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 3, min value 0, current value 33.33%"); | ||
await __1.virtual.stop(); | ||
}); | ||
it("should announce aria-valuenow on scrollbar roles as a zero percentage of a positive aria-valuemin - aria-valuemax range", async () => { | ||
document.body.innerHTML = ` | ||
<span id="element1">Label</span> | ||
<span role="scrollbar" aria-labelledby="element1" aria-valuenow="1" aria-valuemin="1" aria-valuemax="3"> | ||
<span role="scrollbar" aria-controls="scrollable-target" aria-labelledby="element1" aria-valuenow="1" aria-valuemin="1" aria-valuemax="3"> | ||
<svg width="300" height="10"> | ||
@@ -256,2 +219,3 @@ <rect height="10" width="100" stroke="black" fill="red" /> | ||
</span> | ||
<span id="scrollable-target">Scrollable Region</span> | ||
`; | ||
@@ -261,3 +225,3 @@ await __1.virtual.start({ container: document.body }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 3, min value 1, current value 0%"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 3, min value 1, 1 control, current value 0%"); | ||
await __1.virtual.stop(); | ||
@@ -268,3 +232,3 @@ }); | ||
<span id="element1">Label</span> | ||
<span role="scrollbar" aria-labelledby="element1" aria-valuenow="2" aria-valuemin="1" aria-valuemax="3"> | ||
<span role="scrollbar" aria-controls="scrollable-target" aria-labelledby="element1" aria-valuenow="2" aria-valuemin="1" aria-valuemax="3"> | ||
<svg width="300" height="10"> | ||
@@ -275,2 +239,3 @@ <rect height="10" width="100" stroke="black" fill="red" /> | ||
</span> | ||
<span id="scrollable-target">Scrollable Region</span> | ||
`; | ||
@@ -280,3 +245,3 @@ await __1.virtual.start({ container: document.body }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 3, min value 1, current value 50%"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 3, min value 1, 1 control, current value 50%"); | ||
await __1.virtual.stop(); | ||
@@ -287,3 +252,3 @@ }); | ||
<span id="element1">Label</span> | ||
<span role="scrollbar" aria-labelledby="element1" aria-valuenow="2" aria-valuemin="0" aria-valuemax="3"> | ||
<span role="scrollbar" aria-controls="scrollable-target" aria-labelledby="element1" aria-valuenow="2" aria-valuemin="0" aria-valuemax="3"> | ||
<svg width="300" height="10"> | ||
@@ -294,2 +259,3 @@ <rect height="10" width="100" stroke="black" fill="red" /> | ||
</span> | ||
<span id="scrollable-target">Scrollable Region</span> | ||
`; | ||
@@ -299,3 +265,3 @@ await __1.virtual.start({ container: document.body }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 3, min value 0, current value 66.67%"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 3, min value 0, 1 control, current value 66.67%"); | ||
await __1.virtual.stop(); | ||
@@ -306,3 +272,3 @@ }); | ||
<span id="element1">Label</span> | ||
<span role="scrollbar" aria-labelledby="element1" aria-valuenow="2" aria-valuemin="-2" aria-valuemax="3"> | ||
<span role="scrollbar" aria-controls="scrollable-target" aria-labelledby="element1" aria-valuenow="2" aria-valuemin="-2" aria-valuemax="3"> | ||
<svg width="300" height="10"> | ||
@@ -313,2 +279,3 @@ <rect height="10" width="100" stroke="black" fill="red" /> | ||
</span> | ||
<span id="scrollable-target">Scrollable Region</span> | ||
`; | ||
@@ -318,3 +285,3 @@ await __1.virtual.start({ container: document.body }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 3, min value -2, current value 80%"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("scrollbar, Label, orientated vertically, max value 3, min value -2, 1 control, current value 80%"); | ||
await __1.virtual.stop(); | ||
@@ -321,0 +288,0 @@ }); |
@@ -32,3 +32,3 @@ "use strict"; | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("checkbox, Horns"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("checkbox, Horns, not checked"); | ||
await __1.virtual.stop(); | ||
@@ -59,3 +59,3 @@ }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("checkbox, Horns"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("checkbox, Horns, not checked"); | ||
await __1.virtual.stop(); | ||
@@ -109,3 +109,3 @@ }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("checkbox, Horns"); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("checkbox, Horns, not checked"); | ||
await __1.virtual.stop(); | ||
@@ -187,2 +187,11 @@ }); | ||
}); | ||
it("should ignore an invalid 'checked' property on an element + properties combination that doesn't support it", async () => { | ||
document.body.innerHTML = ` | ||
<input role="checkbox" type="text" value="Some text" checked /> | ||
`; | ||
await __1.virtual.start({ container: document.body }); | ||
await __1.virtual.next(); | ||
expect(await __1.virtual.lastSpokenPhrase()).toBe("checkbox, Some text"); | ||
await __1.virtual.stop(); | ||
}); | ||
}); |
@@ -303,5 +303,5 @@ "use strict"; | ||
"button, x", | ||
"checkbox", | ||
"checkbox, not checked", | ||
"textbox", | ||
"radio", | ||
"radio, not checked", | ||
"slider, 50, orientated horizontally, max value 100, min value 0", | ||
@@ -308,0 +308,0 @@ "button, x", |
{ | ||
"name": "@guidepup/virtual-screen-reader", | ||
"version": "0.14.0", | ||
"version": "0.15.0", | ||
"description": "Virtual screen reader driver for unit test automation.", | ||
@@ -41,6 +41,6 @@ "main": "lib/index.js", | ||
"@testing-library/jest-dom": "^6.1.4", | ||
"@types/jest": "^29.5.7", | ||
"@types/node": "^20.8.10", | ||
"@typescript-eslint/eslint-plugin": "^6.9.1", | ||
"@typescript-eslint/parser": "^6.9.1", | ||
"@types/jest": "^29.5.8", | ||
"@types/node": "^20.9.0", | ||
"@typescript-eslint/eslint-plugin": "^6.10.0", | ||
"@typescript-eslint/parser": "^6.10.0", | ||
"eslint": "^8.53.0", | ||
@@ -47,0 +47,0 @@ "eslint-config-prettier": "^9.0.0", |
@@ -132,2 +132,3 @@ <h1 align="center">Virtual Screen Reader</h1> | ||
- [`@guidepup/playwright`](https://github.com/guidepup/guidepup-playwright/) - seamless integration of Guidepup with Playwright. | ||
- [`@guidepup/jest`](https://github.com/guidepup/jest/) - jest matchers for reliable unit testing of your screen reader a11y workflows. | ||
@@ -134,0 +135,0 @@ ## Similar |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
340385
8208
146
0