-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9102a5a
commit 9ad71b1
Showing
10 changed files
with
298 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { getNextIndexByIdRefsAttribute } from "./getNextIndexByIdRefsAttribute"; | ||
import { VirtualCommandArgs } from "./types"; | ||
|
||
export interface JumpToErrorMessageElementCommandArgs | ||
extends VirtualCommandArgs { | ||
index?: number; | ||
} | ||
|
||
/** | ||
* aria-errormessage: | ||
* | ||
* REFs: | ||
* - https://www.w3.org/TR/wai-aria-1.2/#aria-errormessage | ||
* - https://a11ysupport.io/tech/aria/aria-errormessage_attribute | ||
*/ | ||
export function jumpToErrorMessageElement({ | ||
index = 0, | ||
container, | ||
currentIndex, | ||
tree, | ||
}: JumpToErrorMessageElementCommandArgs) { | ||
return getNextIndexByIdRefsAttribute({ | ||
attributeName: "aria-errormessage", | ||
index, | ||
container, | ||
currentIndex, | ||
tree, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { virtual } from "../.."; | ||
|
||
describe("Aria Error Message", () => { | ||
beforeEach(async () => { | ||
document.body.innerHTML = ` | ||
<!-- Initial valid state --> | ||
<label for="invalid-false">Input with aria-invalid="false"</label> | ||
<input id="invalid-false" type="text" aria-errormessage="invalid-false-msg" value="" aria-invalid="false"> | ||
<div id="invalid-false-msg" style="visibility:hidden">example error text</div> | ||
<!-- User has input an invalid value --> | ||
<label for="invalid-true">Input with aria-invalid="true"</label> | ||
<input id="invalid-true" type="text" aria-errormessage="invalid-true-msg" aria-invalid="true" value="" > | ||
<div id="invalid-true-msg">example error text</div> | ||
<h2>Reference input with aria-invalid="true" but no aria-errormessage</h2> | ||
<p>It may not always be clear if aria-invalid="true" is being conveyed" or if aria-errormessage is being conveyed, or both. So the following is used as a reference.</p> | ||
<label for="reference-input">Reference input</label> | ||
<input id="reference-input" type="text" aria-invalid="true" value=""> | ||
`; | ||
|
||
await virtual.start({ container: document.body }); | ||
}); | ||
|
||
afterEach(async () => { | ||
await virtual.stop(); | ||
document.body.innerHTML = ``; | ||
}); | ||
|
||
it('should not convey the error when the error message is NOT pertinent - applied to the input[type="text"] element', async () => { | ||
document.querySelector<HTMLInputElement>("#invalid-false")!.focus(); | ||
|
||
expect(await virtual.spokenPhraseLog()).toEqual([ | ||
"document", | ||
'textbox, Input with aria-invalid="false", not invalid', | ||
]); | ||
}); | ||
|
||
it('should convey that the referenced error message is pertinent - applied to the input[type="text"] element', async () => { | ||
document.querySelector<HTMLInputElement>("#invalid-true")!.focus(); | ||
|
||
expect(await virtual.spokenPhraseLog()).toEqual([ | ||
"document", | ||
'textbox, Input with aria-invalid="true", 1 error message, invalid', | ||
]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
import { virtual } from "../.."; | ||
|
||
describe("jumpToErrorMessageElement", () => { | ||
afterEach(async () => { | ||
await virtual.stop(); | ||
document.body.innerHTML = ""; | ||
}); | ||
|
||
it("should jump to an error message element", async () => { | ||
document.body.innerHTML = ` | ||
<label for="invalid-true">Input with aria-invalid="true"</label> | ||
<input id="invalid-true" type="text" aria-errormessage="invalid-true-msg" aria-invalid="true" value="" > | ||
<div id="invalid-true-msg">example error text</div> | ||
`; | ||
|
||
await virtual.start({ container: document.body }); | ||
await virtual.next(); | ||
await virtual.next(); | ||
await virtual.perform(virtual.commands.jumpToErrorMessageElement); | ||
|
||
expect(await virtual.spokenPhraseLog()).toEqual([ | ||
"document", | ||
'Input with aria-invalid="true"', | ||
'textbox, Input with aria-invalid="true", 1 error message, invalid', | ||
"example error text", | ||
]); | ||
}); | ||
|
||
it("should jump to the second error message element", async () => { | ||
document.body.innerHTML = ` | ||
<label for="invalid-true">Input with aria-invalid="true"</label> | ||
<input id="invalid-true" type="text" aria-errormessage="invalid-true-msg-1 invalid-true-msg-2" aria-invalid="true" value="" > | ||
<div id="invalid-true-msg-1">first example error text</div> | ||
<div id="invalid-true-msg-2">second example error text</div> | ||
`; | ||
|
||
await virtual.start({ container: document.body }); | ||
await virtual.next(); | ||
await virtual.next(); | ||
await virtual.perform(virtual.commands.jumpToErrorMessageElement, { | ||
index: 1, | ||
}); | ||
|
||
expect(await virtual.spokenPhraseLog()).toEqual([ | ||
"document", | ||
'Input with aria-invalid="true"', | ||
'textbox, Input with aria-invalid="true", 2 error messages, invalid', | ||
"second example error text", | ||
]); | ||
}); | ||
|
||
it("should jump to an error message presentation element", async () => { | ||
document.body.innerHTML = ` | ||
<label for="invalid-true">Input with aria-invalid="true"</label> | ||
<input id="invalid-true" type="text" aria-errormessage="invalid-true-msg-1 invalid-true-msg-2" aria-invalid="true" value="" > | ||
<div id="invalid-true-msg-1" role="presentation">first example error text</div> | ||
<div id="invalid-true-msg-2" role="presentation">second example error text</div> | ||
`; | ||
|
||
await virtual.start({ container: document.body }); | ||
await virtual.next(); | ||
await virtual.next(); | ||
await virtual.perform(virtual.commands.jumpToErrorMessageElement); | ||
|
||
expect(await virtual.spokenPhraseLog()).toEqual([ | ||
"document", | ||
'Input with aria-invalid="true"', | ||
'textbox, Input with aria-invalid="true", 2 error messages, invalid', | ||
"first example error text", | ||
]); | ||
}); | ||
|
||
it("should jump to a second error message presentation element", async () => { | ||
document.body.innerHTML = ` | ||
<label for="invalid-true">Input with aria-invalid="true"</label> | ||
<input id="invalid-true" type="text" aria-errormessage="invalid-true-msg-1 invalid-true-msg-2" aria-invalid="true" value="" > | ||
<div id="invalid-true-msg-1" role="presentation">first example error text</div> | ||
<div id="invalid-true-msg-2" role="presentation">second example error text</div> | ||
`; | ||
|
||
await virtual.start({ container: document.body }); | ||
await virtual.next(); | ||
await virtual.next(); | ||
await virtual.perform(virtual.commands.jumpToErrorMessageElement, { | ||
index: 1, | ||
}); | ||
|
||
expect(await virtual.spokenPhraseLog()).toEqual([ | ||
"document", | ||
'Input with aria-invalid="true"', | ||
'textbox, Input with aria-invalid="true", 2 error messages, invalid', | ||
"second example error text", | ||
]); | ||
}); | ||
|
||
it("should handle a non-element container gracefully", async () => { | ||
const container = document.createTextNode("text node"); | ||
|
||
await virtual.start({ container }); | ||
await virtual.perform(virtual.commands.jumpToErrorMessageElement); | ||
|
||
expect(await virtual.spokenPhraseLog()).toEqual(["text node"]); | ||
|
||
await virtual.stop(); | ||
}); | ||
|
||
it("should handle a hidden container gracefully", async () => { | ||
const container = document.createElement("div"); | ||
container.setAttribute("aria-hidden", "true"); | ||
|
||
await virtual.start({ container }); | ||
await virtual.perform(virtual.commands.jumpToErrorMessageElement); | ||
|
||
expect(await virtual.spokenPhraseLog()).toEqual([]); | ||
|
||
await virtual.stop(); | ||
}); | ||
|
||
it("should ignore the command on a non-element node", async () => { | ||
document.body.innerHTML = `Hello World`; | ||
|
||
await virtual.start({ container: document.body }); | ||
await virtual.next(); | ||
await virtual.perform(virtual.commands.jumpToErrorMessageElement); | ||
|
||
expect(await virtual.spokenPhraseLog()).toEqual([ | ||
"document", | ||
"Hello World", | ||
]); | ||
|
||
await virtual.stop(); | ||
}); | ||
|
||
it("should ignore the command on an element with no aria-errormessage", async () => { | ||
document.body.innerHTML = ` | ||
<button id="target">Target</button> | ||
`; | ||
|
||
await virtual.start({ container: document.body }); | ||
await virtual.next(); | ||
await virtual.perform(virtual.commands.jumpToErrorMessageElement); | ||
|
||
expect(await virtual.spokenPhraseLog()).toEqual([ | ||
"document", | ||
"button, Target", | ||
]); | ||
|
||
await virtual.stop(); | ||
}); | ||
|
||
it("should ignore the command on an element with an invalid aria-errormessage", async () => { | ||
document.body.innerHTML = ` | ||
<label for="invalid-true">Input with aria-invalid="true"</label> | ||
<input id="invalid-true" type="text" aria-errormessage="missing-element" aria-invalid="true" value="" > | ||
`; | ||
|
||
await virtual.start({ container: document.body }); | ||
await virtual.next(); | ||
await virtual.next(); | ||
await virtual.perform(virtual.commands.jumpToErrorMessageElement); | ||
|
||
expect(await virtual.spokenPhraseLog()).toEqual([ | ||
"document", | ||
'Input with aria-invalid="true"', | ||
'textbox, Input with aria-invalid="true", invalid', | ||
]); | ||
|
||
await virtual.stop(); | ||
}); | ||
|
||
it("should ignore the command on an element with an aria-errormessage pointing to a hidden element", async () => { | ||
document.body.innerHTML = ` | ||
<label for="invalid-true">Input with aria-invalid="true"</label> | ||
<input id="invalid-true" type="text" aria-errormessage="invalid-true-msg" aria-invalid="true" value="" > | ||
<div id="invalid-true-msg" aria-hidden="true">example error text</div> | ||
`; | ||
|
||
await virtual.start({ container: document.body }); | ||
await virtual.next(); | ||
await virtual.next(); | ||
await virtual.perform(virtual.commands.jumpToErrorMessageElement); | ||
|
||
expect(await virtual.spokenPhraseLog()).toEqual([ | ||
"document", | ||
'Input with aria-invalid="true"', | ||
'textbox, Input with aria-invalid="true", 1 error message, invalid', | ||
]); | ||
}); | ||
}); |