-
Notifications
You must be signed in to change notification settings - Fork 9
Accessibility improvements: add missing labels for input fields #2970
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
base: develop
Are you sure you want to change the base?
Changes from all commits
8368663
08d747b
6dbfb9e
01eeeeb
830bb41
b39bef5
83fcebe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,217 @@ | ||||||
| package org.labkey.test.components.ui.entities; | ||||||
|
|
||||||
| import org.apache.commons.lang3.StringUtils; | ||||||
| import org.labkey.api.util.Pair; | ||||||
| import org.labkey.test.Locator; | ||||||
| import org.labkey.test.components.bootstrap.ModalDialog; | ||||||
| import org.labkey.test.components.html.Checkbox; | ||||||
| import org.labkey.test.components.html.Input; | ||||||
| import org.labkey.test.components.react.FilteringReactSelect; | ||||||
| import org.labkey.test.components.react.ReactDateTimePicker; | ||||||
| import org.labkey.test.components.react.ReactSelect; | ||||||
| import org.labkey.test.components.react.ToggleButton; | ||||||
| import org.labkey.test.components.ui.files.FileAttachmentContainer; | ||||||
| import org.labkey.test.params.FieldKey; | ||||||
| import org.openqa.selenium.WebElement; | ||||||
|
|
||||||
| import java.util.HashMap; | ||||||
| import java.util.Map; | ||||||
|
|
||||||
| import static org.labkey.test.WebDriverWrapper.WAIT_FOR_JAVASCRIPT; | ||||||
|
|
||||||
| /** | ||||||
| * Abstract base for {@link EntityBulkInsertDialog} and {@link EntityBulkUpdateDialog}. | ||||||
| */ | ||||||
| public abstract class EntityBulkDialog extends ModalDialog | ||||||
| { | ||||||
| protected int _changeCounter = 0; | ||||||
|
|
||||||
| protected EntityBulkDialog(ModalDialogFinder finder) | ||||||
| { | ||||||
| super(finder); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * @param fieldIdentifier Identifier for the field; name ({@link String}) or fieldKey ({@link FieldKey}) | ||||||
| * @return current value of the specified field | ||||||
| */ | ||||||
| public String getTextArea(CharSequence fieldIdentifier) | ||||||
| { | ||||||
| return elementCache().textArea(fieldIdentifier).get(); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * @param fieldIdentifier Identifier for the field; name ({@link String}) or fieldKey ({@link FieldKey}) | ||||||
| * @return current value of the specified field | ||||||
| */ | ||||||
| public String getNumericField(CharSequence fieldIdentifier) | ||||||
| { | ||||||
| return elementCache().textInput(fieldIdentifier).get(); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * @param fieldIdentifier Identifier for the field; name ({@link String}) or fieldKey ({@link FieldKey}) | ||||||
| * @return current value of the specified field | ||||||
| */ | ||||||
| public boolean getBooleanField(CharSequence fieldIdentifier) | ||||||
| { | ||||||
| return elementCache().checkBox(fieldIdentifier).get(); | ||||||
| } | ||||||
|
|
||||||
| public String getFieldValue(WebElement input) | ||||||
| { | ||||||
| String value = input.getText(); | ||||||
| if (StringUtils.isEmpty(value)) | ||||||
| value = input.getAttribute("value"); | ||||||
| if (StringUtils.isEmpty(value)) | ||||||
| value = input.getAttribute("placeholder"); | ||||||
| if (StringUtils.isEmpty(value) && "checkbox".equals(input.getAttribute("type"))) | ||||||
| value = input.getAttribute("title"); | ||||||
| return value; | ||||||
| } | ||||||
|
|
||||||
| protected String getValueForReactSelect(ReactSelect reactSelect) | ||||||
| { | ||||||
| if (!reactSelect.getSelections().isEmpty()) | ||||||
| { | ||||||
| return reactSelect.getSelections().get(0); | ||||||
| } | ||||||
| else | ||||||
| { | ||||||
| return ""; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| private WebElement getAmountUnitsRow() | ||||||
| { | ||||||
| String fieldLabel = "Amount and Units"; | ||||||
| return elementCache().formRowByControlLabel(fieldLabel); | ||||||
| } | ||||||
|
|
||||||
| public Pair<String, String> getAmountUnit() | ||||||
| { | ||||||
| String amountVal = getFieldValue(getAmountInput()); | ||||||
| String unitVal = Locator.tagWithClass("div", "select-input__value-container").withoutAttribute("type", "hidden").findElement(getAmountUnitsRow()).getText(); | ||||||
| return new Pair<>(amountVal, unitVal); | ||||||
| } | ||||||
|
|
||||||
| public Pair<String, String> getAmountUnitValue() | ||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
| { | ||||||
| enableAmountUnit(); | ||||||
| String amountVal = getWrapper().getFormElement(getAmountInput()); | ||||||
| String unitVal = getValueForReactSelect(getAmountUnitSelect()); | ||||||
| return new Pair<>(amountVal, unitVal); | ||||||
| } | ||||||
|
|
||||||
| public void enableAmountUnit() | ||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| { | ||||||
| ToggleButton toggle = new ToggleButton.ToggleButtonFinder(getDriver()).findOrNull(getAmountUnitsRow()); | ||||||
| if (toggle != null && !toggle.isOn()) | ||||||
| { | ||||||
| toggle.set(true); | ||||||
| _changeCounter++; | ||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is |
||||||
| } | ||||||
| } | ||||||
|
|
||||||
| public void disableAmountUnit() | ||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| { | ||||||
| ToggleButton toggle = new ToggleButton.ToggleButtonFinder(getDriver()).findOrNull(getAmountUnitsRow()); | ||||||
| if(toggle != null && toggle.isOn()) | ||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| { | ||||||
| toggle.set(false); | ||||||
| _changeCounter--; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| private WebElement getAmountInput() | ||||||
| { | ||||||
| return elementCache().amountInputLoc.findElement(getAmountUnitsRow()); | ||||||
| } | ||||||
|
|
||||||
| public ReactSelect getAmountUnitSelect() | ||||||
| { | ||||||
| enableAmountUnit(); | ||||||
| return new ReactSelect(getAmountUnitsRow(), getDriver()); | ||||||
| } | ||||||
|
|
||||||
| public void setAmountUnit(String amount, String unit) | ||||||
| { | ||||||
| enableAmountUnit(); | ||||||
|
|
||||||
| if (amount != null) | ||||||
| getWrapper().setFormElement(getAmountInput(), amount); | ||||||
| if (unit != null) | ||||||
| { | ||||||
| ReactSelect reactSelect = getAmountUnitSelect(); | ||||||
| if (!unit.isEmpty()) | ||||||
| reactSelect.select(unit); | ||||||
| else | ||||||
| reactSelect.clearSelection(); | ||||||
| } | ||||||
|
|
||||||
| if (amount != null && unit != null) | ||||||
| _changeCounter++; | ||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question here about whether this is one change or two. |
||||||
| } | ||||||
|
|
||||||
| @Override | ||||||
| protected abstract ElementCache newElementCache(); | ||||||
|
|
||||||
| @Override | ||||||
| protected ElementCache elementCache() | ||||||
| { | ||||||
| return (ElementCache) super.elementCache(); | ||||||
| } | ||||||
|
|
||||||
| protected abstract class ElementCache extends ModalDialog.ElementCache | ||||||
| { | ||||||
| protected final Locator textInputLoc = Locator.tagWithAttribute("input", "type", "text"); | ||||||
| protected final Locator checkBoxLoc = Locator.tagWithAttribute("input", "type", "checkbox"); | ||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| protected final Locator.XPathLocator amountInputLoc = Locator.tag("input").withAttribute("aria-label", "Amount"); | ||||||
|
|
||||||
| protected final Map<String, WebElement> _rows = new HashMap<>(); | ||||||
|
|
||||||
| /** | ||||||
| * Returns the form row div that contains the controls for the given field. | ||||||
| */ | ||||||
| public abstract WebElement formRow(CharSequence fieldIdentifier); | ||||||
|
|
||||||
| // For composite fields (e.g. StoredAmount + Units) that render a <div> label instead of <label for="...">, | ||||||
| private WebElement formRowByControlLabel(String fieldLabel) | ||||||
| { | ||||||
| return _rows.computeIfAbsent(fieldLabel, k -> | ||||||
| Locator.tagWithClass("div", "row") | ||||||
| .withChild(Locator.tagWithClass("div", "control-label").withText(fieldLabel)) | ||||||
| .waitForElement(this, WAIT_FOR_JAVASCRIPT)); | ||||||
| } | ||||||
|
|
||||||
| public FilteringReactSelect selectionField(CharSequence fieldIdentifier) | ||||||
| { | ||||||
| return new FilteringReactSelect(formRow(fieldIdentifier), getDriver()); | ||||||
| } | ||||||
|
|
||||||
| public Checkbox checkBox(CharSequence fieldIdentifier) | ||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| { | ||||||
| return new Checkbox(checkBoxLoc.findElement(formRow(fieldIdentifier))); | ||||||
| } | ||||||
|
|
||||||
| public Input textInput(CharSequence fieldIdentifier) | ||||||
| { | ||||||
| return new Input(textInputLoc.findElement(formRow(fieldIdentifier)), getDriver()); | ||||||
| } | ||||||
|
|
||||||
| public Input textArea(CharSequence fieldIdentifier) | ||||||
| { | ||||||
| return new Input(Locator.tag("textarea").findElement(formRow(fieldIdentifier)), getDriver()); | ||||||
| } | ||||||
|
|
||||||
| public ReactDateTimePicker dateInput(CharSequence fieldIdentifier) | ||||||
| { | ||||||
| return new ReactDateTimePicker.ReactDateTimeInputFinder(getDriver()).find(formRow(fieldIdentifier)); | ||||||
| } | ||||||
|
|
||||||
| public FileAttachmentContainer fileUploadField(CharSequence fieldIdentifier) | ||||||
| { | ||||||
| return new FileAttachmentContainer(formRow(fieldIdentifier), getDriver()); | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe
getAmountAndUnitsReadOnlyValuesor something to distinguish it fromgetAmountUnitValue.This could also return a record instead of a pair for better readability, but not necessary.