From 2e7b968d7c9f4a41923eb772d17c8e7dde53d18a Mon Sep 17 00:00:00 2001 From: niyap <38278268+niyap@users.noreply.github.com> Date: Mon, 1 Feb 2021 16:45:27 +0200 Subject: [PATCH] feat(ui5-multi-input): Implement accessibility specifications (#2761) --- packages/main/src/MultiComboBox.hbs | 5 +- packages/main/src/MultiComboBox.js | 32 +++++----- packages/main/src/MultiInput.hbs | 2 +- packages/main/src/MultiInput.js | 27 +++++++++ packages/main/src/Tokenizer.js | 23 ++++++- .../main/src/i18n/messagebundle.properties | 5 +- packages/main/test/pages/MultiComboBox.html | 8 +-- packages/main/test/pages/MultiInput.html | 14 +++++ .../main/test/specs/MultiComboBox.spec.js | 56 +++++++++++++++++ packages/main/test/specs/MultiInput.spec.js | 60 +++++++++++++++++++ 10 files changed, 205 insertions(+), 27 deletions(-) diff --git a/packages/main/src/MultiComboBox.hbs b/packages/main/src/MultiComboBox.hbs index f3c4298d9070..181c33943b45 100644 --- a/packages/main/src/MultiComboBox.hbs +++ b/packages/main/src/MultiComboBox.hbs @@ -1,6 +1,6 @@
- {{nMoreCountText}} + {{_tokensCountText}} {{#if hasValueState}} {{valueStateText}} @@ -50,8 +50,7 @@ aria-haspopup="listbox" aria-expanded="{{open}}" aria-autocomplete="both" - aria-labelledby="{{_id}}-hiddenText-nMore" - aria-describedby="{{valueStateTextId}}" + aria-describedby="{{ariaDescribedByText}}" aria-required="{{required}}" /> diff --git a/packages/main/src/MultiComboBox.js b/packages/main/src/MultiComboBox.js index 120d637a7527..80a4884bca58 100644 --- a/packages/main/src/MultiComboBox.js +++ b/packages/main/src/MultiComboBox.js @@ -31,9 +31,6 @@ import { VALUE_STATE_SUCCESS, VALUE_STATE_ERROR, VALUE_STATE_WARNING, - TOKENIZER_ARIA_CONTAIN_TOKEN, - TOKENIZER_ARIA_CONTAIN_ONE_TOKEN, - TOKENIZER_ARIA_CONTAIN_SEVERAL_TOKENS, INPUT_SUGGESTIONS_TITLE, SELECT_OPTIONS, MULTICOMBOBOX_DIALOG_OK_BUTTON, @@ -752,20 +749,6 @@ class MultiComboBox extends UI5Element { return this.shadowRoot.querySelector("[ui5-tokenizer]"); } - get nMoreCountText() { - const iTokenCount = this._getSelectedItems().length; - - if (iTokenCount === 0) { - return this.i18nBundle.getText(TOKENIZER_ARIA_CONTAIN_TOKEN); - } - - if (iTokenCount === 1) { - return this.i18nBundle.getText(TOKENIZER_ARIA_CONTAIN_ONE_TOKEN); - } - - return this.i18nBundle.getText(TOKENIZER_ARIA_CONTAIN_SEVERAL_TOKENS, iTokenCount); - } - inputFocusIn() { if (!isPhone()) { this.focused = true; @@ -814,6 +797,21 @@ class MultiComboBox extends UI5Element { return this.getSlottedNodes("valueStateMessage").map(el => el.cloneNode(true)); } + get _tokensCountText() { + if (!this._tokenizer) { + return; + } + return this._tokenizer._tokensCountText(); + } + + get _tokensCountTextId() { + return `${this._id}-hiddenText-nMore`; + } + + get ariaDescribedByText() { + return this.valueStateTextId ? `${this._tokensCountTextId} ${this.valueStateTextId}` : `${this._tokensCountTextId}`; + } + get shouldDisplayDefaultValueStateMessage() { return !this.valueStateMessage.length && this.hasValueStateMessage; } diff --git a/packages/main/src/MultiInput.hbs b/packages/main/src/MultiInput.hbs index cd358f5e1513..7ccab7d0f118 100644 --- a/packages/main/src/MultiInput.hbs +++ b/packages/main/src/MultiInput.hbs @@ -1,5 +1,5 @@ {{>include "./Input.hbs"}} - + {{_tokensCountText}} {{#*inline "preContent"}} value state error
- @@ -270,9 +270,9 @@

MultiComboBox in Compact

- - - + + + diff --git a/packages/main/test/pages/MultiInput.html b/packages/main/test/pages/MultiInput.html index 4c5ad954eed5..937e4b60e29e 100644 --- a/packages/main/test/pages/MultiInput.html +++ b/packages/main/test/pages/MultiInput.html @@ -204,6 +204,16 @@

Multi Input - Error

Tokens

+

Multi Input without tokens

+ +
+
+ + Add more tokens + +
+
+

Multi Input with 1 token

@@ -297,6 +307,10 @@

Test value-help-trigger with F4 and Alt + ArrowUp/Down

document.getElementById(id).appendChild(token); } + document.getElementById("add-tokens").addEventListener("click", function(event) { + addTokenToMI(createTokenFromText("test"), "no-tokens"); + }); + document.getElementById("add-to-single").addEventListener("click", function(event) { addTokenToMI(createTokenFromText("test"), "single-token"); }); diff --git a/packages/main/test/specs/MultiComboBox.spec.js b/packages/main/test/specs/MultiComboBox.spec.js index d9fb6376cbfc..3861e9fe31d5 100644 --- a/packages/main/test/specs/MultiComboBox.spec.js +++ b/packages/main/test/specs/MultiComboBox.spec.js @@ -237,4 +237,60 @@ describe("MultiComboBox general interaction", () => { icon.click(); }); }); + + describe("ARIA attributes", () => { + browser.url("http://localhost:8080/test-resources/pages/MultiComboBox.html"); + + it ("aria-describedby value according to the tokens count and the value state", () => { + const mcb = $("#mcb-error"); + const innerInput = mcb.shadow$("input"); + const invisibleText = mcb.shadow$(".ui5-hidden-text"); + let tokens = mcb.shadow$$(".ui5-multi-combobox-token"); + const tokensCountITextId = `${mcb.getProperty("_id")}-hiddenText-nMore`; + const valuestateITextId = `${mcb.getProperty("_id")}-valueStateDesc`; + const ariaDescribedBy = `${tokensCountITextId} ${valuestateITextId}`; + + assert.strictEqual(tokens.length, 3, "should have three tokens"); + assert.strictEqual(innerInput.getAttribute("aria-describedby"), ariaDescribedBy, "aria-describedby has a reference for the value state and the tokens count"); + }); + + it ("aria-describedby value according to the tokens count", () => { + const mcb = $("#mcb-compact"); + const innerInput = mcb.shadow$("input"); + const invisibleText = mcb.shadow$(".ui5-hidden-text"); + const inivisbleTextId = invisibleText.getProperty("id"); + let tokens = mcb.shadow$$(".ui5-multi-combobox-token"); + let resourceBundleText = null; + + assert.strictEqual(tokens.length, 2, "should have two tokens"); + assert.strictEqual(innerInput.getAttribute("aria-describedby"), inivisbleTextId, "aria-describedby reference is correct"); + assert.strictEqual(invisibleText.getText(), "Contains 2 tokens", "aria-describedby text is correct"); + + mcb.scrollIntoView(); + innerInput.click(); + innerInput.keys("Backspace"); + innerInput.keys("Backspace"); + + tokens = mcb.shadow$$(".ui5-multi-combobox-token"); + + resourceBundleText = browser.execute(() => { + const mcb = document.getElementById("mcb-compact"); + return mcb.i18nBundle.getText("TOKENIZER_ARIA_CONTAIN_ONE_TOKEN"); + }); + + assert.strictEqual(tokens.length, 1, "should have one token"); + assert.strictEqual(invisibleText.getText(), resourceBundleText, "aria-describedby text is correct"); + + innerInput.keys("Backspace"); + + tokens = mcb.shadow$$(".ui5-multi-combobox-token"); + resourceBundleText = browser.execute(() => { + const mcb = document.getElementById("mcb-compact"); + return mcb.i18nBundle.getText("TOKENIZER_ARIA_CONTAIN_TOKEN"); + }); + + assert.strictEqual(tokens.length, 0, "should not have tokens"); + assert.strictEqual(invisibleText.getText(), resourceBundleText, "aria-describedby text is correct"); + }); + }); }); diff --git a/packages/main/test/specs/MultiInput.spec.js b/packages/main/test/specs/MultiInput.spec.js index 0af0e8f482f4..0ab998547b55 100644 --- a/packages/main/test/specs/MultiInput.spec.js +++ b/packages/main/test/specs/MultiInput.spec.js @@ -124,3 +124,63 @@ describe("MultiInput general interaction", () => { assert.strictEqual(mi.$$("ui5-token").length, 1, "a token is added after selection"); }); }); + +describe("ARIA attributes", () => { + it ("aria-describedby value according to the tokens count", () => { + const mi = $("#no-tokens"); + const innerInput = mi.shadow$("input"); + const btn = $("#add-tokens"); + const invisibleText = mi.shadow$(".ui5-hidden-text"); + const inivisbleTextId = invisibleText.getProperty("id"); + let resourceBundleText = null; + + resourceBundleText = browser.execute(() => { + const mi = document.getElementById("no-tokens"); + return mi.i18nBundle.getText("TOKENIZER_ARIA_CONTAIN_TOKEN"); + }); + + assert.strictEqual(mi.$$("ui5-token").length, 0, "should not have tokens"); + assert.strictEqual(innerInput.getAttribute("aria-describedby"), inivisbleTextId, "aria-describedby reference is correct"); + assert.strictEqual(invisibleText.getText(), resourceBundleText, "aria-describedby text is correct"); + + $("#add-tokens").scrollIntoView(); + btn.click(); + + resourceBundleText = browser.execute(() => { + const mi = document.getElementById("no-tokens"); + return mi.i18nBundle.getText("TOKENIZER_ARIA_CONTAIN_ONE_TOKEN"); + }); + + assert.strictEqual(mi.$$("ui5-token").length, 1, "should have one token"); + assert.strictEqual(invisibleText.getText(), resourceBundleText, "aria-describedby text is correct"); + + btn.click(); + assert.strictEqual(mi.$$("ui5-token").length, 2, "should have two tokens"); + assert.strictEqual(invisibleText.getText(), "Contains 2 tokens", "aria-describedby text is correct"); + }); + + it ("aria-describedby value according to the tokens and suggestions count", () => { + const mi = $("#suggestion-token"); + const innerInput = mi.shadow$("input"); + const tokensCountITextId = `${mi.getProperty("_id")}-hiddenText-nMore`; + const suggestionsITextId = `${mi.getProperty("_id")}-suggestionsText`; + const suggestionsCountITextId = `${mi.getProperty("_id")}-suggestionsCount`; + const ariaDescribedBy = `${tokensCountITextId} ${suggestionsITextId} ${suggestionsCountITextId}`; + + $("#suggestion-token").scrollIntoView(); + innerInput.click(); + innerInput.keys("a"); + innerInput.keys("ArrowDown"); + innerInput.keys("Enter"); + + assert.strictEqual(innerInput.getAttribute("aria-describedby"), ariaDescribedBy, "aria-describedby attribute contains multiple references"); + }); + + it ("aria-roledescription is set properly", () => { + const mi = $("#no-tokens"); + const innerInput = mi.shadow$("input"); + + assert.strictEqual(innerInput.getAttribute("aria-roledescription"), "Multi Value Input", "aria-roledescription value is correct"); + }); +}); +