Skip to content

Commit

Permalink
Merge pull request #2736 from Azure/shpaster/a11y-bugs
Browse files Browse the repository at this point in the history
Accessibility bug fixes
  • Loading branch information
gingi committed May 31, 2023
2 parents e099763 + 3c81060 commit 732b86b
Show file tree
Hide file tree
Showing 37 changed files with 492 additions and 182 deletions.
92 changes: 92 additions & 0 deletions desktop/i18n/resources.resjson
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,38 @@
"certificate-references-picker.addOne": "Add a certificate",
"certificate-references-picker.duplicate": "Cannot reference the same certificate twice {thumbprint}",
"certificate-references-picker.noCertificates": "No certificate(s) found for this batch account, please upload your certificate(s) first.",
"common.application": "Application",
"common.apply": "Apply",
"common.back": "Back",
"common.configuration": "Configuration",
"common.confirm": "Confirm",
"common.creationTime": "Creation time",
"common.displayName": "Display name",
"common.done": "Done",
"common.general": "General",
"common.id": "ID",
"common.job": "Job",
"common.jobs": "Jobs",
"common.key": "Key",
"common.lastModified": "Last modified",
"common.learnMore": "Learn more",
"common.listbox": "Listbox",
"common.loading": "Loading",
"common.metadata": "Metadata",
"common.no": "No",
"common.none": "None",
"common.pool": "Pool",
"common.pools": "Pools",
"common.rebootAll": "Reboot all",
"common.refresh": "Refresh",
"common.refreshing": "Refreshing",
"common.required": "Required",
"common.state": "State",
"common.task": "Task",
"common.tasks": "Tasks",
"common.updated": "Updated",
"common.value": "Value",
"common.version": "Version",
"common.yes": "Yes",
"create-new-aad-app.create": "Create",
"create-new-aad-app.secret.label": "Secret",
Expand Down Expand Up @@ -108,6 +126,7 @@
"entity-command.confirm.single.title": "Are you sure you want to {action} this {type}",
"entity-configuration.viewAsJSON": "View as JSON",
"entity-configuration.viewClassic": "Classic view",
"entity-details-list.toggleAdvancedFilter": "Toggle advanced filter",
"file-explorer.collapseAll": "Collapse all",
"file-explorer.noFiles": "No files",
"file-explorer.noPathMatch": "No files found for \"{path}\"",
Expand Down Expand Up @@ -144,6 +163,72 @@
"job-state.disabling.message": "Job is in the process of being disabled",
"job-state.enabling.message": "Job is returning from disabled stated",
"job-state.terminating.message": "Job is terminating",
"job.desc.generalInfo": "Basic information about the job.",
"job.desc.jobAdvancedSettings": "Specify job advanced settings including constraints, environment settings, etc.",
"job.desc.jobConfigurationTask": "Specify job manager task, job preparation task and job release task. A job release task cannot be specified without a job preparation task.",
"job.desc.poolSelection": "Choose where the job should be running.",
"job.error.belowMin": "Must be greater than or equal to {min}",
"job.error.failedTask": "Job was terminated because a task failed.",
"job.error.idPattern": "Can only contain any combination of alphanumeric characters, including hyphens and underscores",
"job.error.maxLength": "Maximum length of {max} characters",
"job.error.notInRange": "Values can range from {min} to {max}",
"job.error.poolRequired": "You must select a pool for this job to run on",
"job.error.timedOut": "Job timed out after running for {time}.",
"job.hint.nOfMaxCharacters": "{num} out of {max} characters",
"job.label.addJob": "Add a job",
"job.label.commandLine": "Command line",
"job.label.commonEnvVars": "Common environment variables",
"job.label.completedInTime": "Completed in {time}",
"job.label.constraints": "Constraints",
"job.label.containerImageName": "Container image name",
"job.label.containerRunOptions": "Container run options",
"job.label.containerSettings": "Container settings",
"job.label.currentlyActiveFor": "Currently active for {time}",
"job.label.displayName": "Display name",
"job.label.enableTaskDeps": "Enable task dependencies",
"job.label.endTime": "End time",
"job.label.envSettingCount": "{count} environment settings",
"job.label.environmentSettings": "Environment settings",
"job.label.environmentVariables": "Environment variables",
"job.label.executionInfo": "Execution information",
"job.label.executionTime": "Execution time",
"job.label.generalInfo": "General info",
"job.label.hasNoEnvVars": "This job contains no environment settings",
"job.label.hasNoMetadata": "This job contains no metadata",
"job.label.hasNoTags": "No tags",
"job.label.jobAdvancedSettings": "Job advanced settings",
"job.label.jobConfigurationTask": "Job configuration task",
"job.label.jobManagerTask": "Job manager task",
"job.label.jobNotStarted": "Job not started",
"job.label.jobPreparationTask": "Job preparation task",
"job.label.jobReleaseTask": "Job release task",
"job.label.jobStatistics": "Job statistics",
"job.label.killJobOnCompletion": "Kill job on completion",
"job.label.maxTaskRetryCount": "Max task retry count",
"job.label.maxWallClockTime": "Max wall-clock time",
"job.label.metadata": "Metadata",
"job.label.metadataItemCount": "{count} metadata items",
"job.label.onAllTasksComplete": "When all tasks complete",
"job.label.onTaskFailure": "When a task fails",
"job.label.packageReferences": "Package references",
"job.label.poolSelection": "Pool selection",
"job.label.prepReleaseTasks": "Prep/Release tasks",
"job.label.previousState": "Previous state",
"job.label.previousStateTransitionTime": "Previous state transition time",
"job.label.priority": "Priority",
"job.label.registryServer": "Registry server",
"job.label.registryUsername": "Registry username",
"job.label.requiredSlots": "Required slots",
"job.label.rerunOnNodeReboot": "Rerun on node reboot",
"job.label.retentionTime": "Retention time",
"job.label.runElevated": "Run elevated",
"job.label.runExclusive": "Run exclusive",
"job.label.startTime": "Start time",
"job.label.stateTransitionTime": "State transition time",
"job.label.taskDependencies": "Task dependencies",
"job.label.terminationReason": "Termination reason",
"job.label.viewFailedTasks": "View failed tasks",
"job.label.waitForSuccess": "Wait for success",
"list-base.filterApplied": "New filter applied, {count} results",
"list-base.filterCleared": "Filter cleared, {count} results",
"location-picker.noLocationInSubscription": "No locations available in subscription '{name}'",
Expand Down Expand Up @@ -239,8 +324,11 @@
"pool-os-picker.category.rendering": "Graphics and rendering",
"pool-os-picker.containerConfiguration": "Container configuration",
"pool-os-picker.nodeAgentSelection": "Based on your selection, node agent '{sku}' will be used",
"pool-picker.coreCount": "core count",
"pool-picker.description": "Showing pools with a container image matching {filters}",
"pool-picker.filterByID": "Search by id",
"pool-picker.filterByOS": "Filter by OS",
"pool-picker.nodeCount": "node count",
"pool-picker.noneMatching": "You don't have any pools matching the current filters.",
"pool-picker.reset": "Reset",
"pool-scale-picker.evaluationInterval.label": "Formula evaluation interval",
Expand Down Expand Up @@ -287,7 +375,10 @@
"resourcefile-picker.cloudFileDialog.title": "Pick file(s) from storage account",
"select-dropdown.noMatch": "No match",
"select-dropdown.noOptions": "No options available",
"select-dropdown.selected": "Selected",
"select-dropdown.showItems": "Showing {num} items",
"select-dropdown.unselectAll": "Unselect all",
"select-dropdown.unselected": "Unselected",
"server-error.debug-button-label": "Toggle troubleshooting info",
"settings.advancedSettings": "Advanced settings",
"settings.gallerySettings": "Gallery settings",
Expand Down Expand Up @@ -370,6 +461,7 @@
"time-range-picker.quickRange": "Quick range",
"time-range-picker.timeRange": "Time range",
"timezone-dropdown.tooltip": "Select the timezone for dates",
"toast.dismiss-button.label": "Dismiss",
"user-account-picker.gid": "User group id",
"user-account-picker.linuxConfiguration": "Linux specific configuration",
"user-account-picker.loginMode": "Login mode",
Expand Down
18 changes: 18 additions & 0 deletions desktop/src/@batch-flask/ui/common.i18n.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
# To put here some common translations that are reused across the app
common:
application: Application
apply: Apply
back: Back
configuration: Configuration
creationTime: Creation time
confirm: Confirm
displayName: Display name
done: Done
general: General
id: ID
job: Job
jobs: Jobs
key: Key
lastModified: Last modified
learnMore: Learn more
listbox: Listbox
loading: Loading
metadata: Metadata
no: "No"
none: None
pool: Pool
pools: Pools
rebootAll: Reboot all
refresh: Refresh
refreshing: Refreshing
required: Required
state: State
task: Task
tasks: Tasks
updated: Updated
value: Value
version: Version
yes: "Yes"
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ describe("DurationPickerComponent", () => {
expect(component.value instanceof Duration).toBe(true);
expect(testComponent.control.value.toISO()).toEqual("P4DT4H");
});

it("should set aria-label to include label, time, and duration", () => {
testComponent.control.setValue(Duration.fromISO("P50D"));
fixture.detectChanges();
expect(de.nativeElement.getAttribute("aria-label")).toEqual("My duration picker: 50 days");
});
});

describe("writing value", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ export class DurationPickerComponent implements FormFieldControl<any>,
@Input()
public label: string;

@HostBinding("attr.aria-label")
public get ariaLabel() { return `${this.label}: ${this.time} ${this.unit}`; }

@Input() public allowUnlimited: boolean = true;
@Input() public defaultDuration: string;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
(ngModelChange)="updateUnit($event)"
[placeholder]="'duration-picker.unit.label' | i18n"
[disabled]="disabled"
attr.aria-label="{{label}} {{'duration-picker.unit.label' | i18n}}">
[aria-label]="ariaLabel">

<bl-option [value]="DurationUnit.Unlimited" [label]="'duration-picker.unit.unlimited' | i18n" *ngIf="allowUnlimited"></bl-option>
<bl-option [value]="DurationUnit.Days" [label]="'duration-picker.unit.days' | i18n"></bl-option>
Expand Down
1 change: 1 addition & 0 deletions desktop/src/@batch-flask/ui/form/input/input.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export class InputDirective implements FormFieldControl<any>, OnChanges, OnDestr
}

@Input()
@HostBinding("attr.id")
get id(): string { return this._id; }
set id(value: string) { this._id = value || this._uid; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<input blInput [formControl]="searchQuery" [placeholder]="filterPlaceholder + ' (Startwith)'"/>
<bl-clickable *ngIf="enableAdvancedFilter"
class="toggle-advanced-filter"
matTooltip="Toggle advanced filter"
title="{{'entity-details-list.toggleAdvancedFilter' | i18n}}"
matTooltipPosition="above"
[routerLink]="baseLink"
[queryParams]="{filter: true}">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
entity-details-list:
toggleAdvancedFilter: Toggle advanced filter
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DropdownModule } from "../dropdown";
import { PersistedNotificationDropdownComponent } from "./persisted-dropdown";
import { ToastComponent } from "./toast";
import { ToastsContainerComponent } from "./toasts-container";
import { I18nUIModule } from "@batch-flask/ui/i18n";

const privateComponents = [
ToastComponent,
Expand All @@ -27,6 +28,7 @@ const publicComponents = [
MaterialModule,
DropdownModule,
ButtonsModule,
I18nUIModule,
],
})
export class NotificationModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ClickableComponent } from "@batch-flask/ui/buttons";
import { Notification, NotificationLevel, NotificationService } from "@batch-flask/ui/notifications";
import { click, mouseenter, mouseleave, mouseup } from "test/utils/helpers";
import { ToastComponent } from "./toast.component";
import { I18nTestingModule } from "@batch-flask/core/testing";

@Component({
template: `<bl-toast [notification]="notification"></bl-toast>`,
Expand All @@ -28,7 +29,7 @@ describe("ToastComponent", () => {
};

TestBed.configureTestingModule({
imports: [],
imports: [I18nTestingModule],
declarations: [ToastComponent, ClickableComponent, TestComponent],
providers: [
{ provide: NotificationService, useValue: notificationServiceSpy },
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/@batch-flask/ui/notifications/toast/toast.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
{{action.name}}
</bl-clickable>
</div>
<bl-clickable class="dismiss-btn" (do)="dismiss()">
<bl-clickable class="dismiss-btn" (do)="dismiss()" title="{{'toast.dismiss-button.label' | i18n}}">
<i class="fa fa-times" aria-hidden="true"></i>
</bl-clickable>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
toast:
dismiss-button:
label: Dismiss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Component, DebugElement } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { By } from "@angular/platform-browser";
import { I18nTestingModule } from "@batch-flask/core/testing";
import {
Notification,
NotificationLevel,
Expand All @@ -26,7 +27,7 @@ describe("Notification", () => {

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NotificationModule],
imports: [NotificationModule, I18nTestingModule],
declarations: [FakeAppComponent],
providers: [
{ provide: PermissionService, useValue: null },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{{label}}
</div>
<div propertyContent>
<table>
<table [attr.aria-label]="label">
<tr class="head" *ngIf="header">
<ng-template [ngTemplateOutlet]="header.content"></ng-template>
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ bl-quick-list-row-render {
.quick-list-row-state {
color: $card-background;
}

.quick-list-row-extra * {
color: $card-background;
}
}

&.focused {
Expand Down Expand Up @@ -141,3 +145,10 @@ bl-quick-list-row-render {
}
}
}

@media (forced-colors: active) {
bl-quick-list-row-render.selected {
outline: 2px solid Highlight;
outline-offset: -2px;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export class SelectDropdownComponent {
fixedOptions = [{
id: unselectAllOptionId,
value: unselectAllOptionId,
label: "Unselect all",
label: this.i18n.t("select-dropdown.unselectAll"),
cssClass: "unselect-all-option"
}];
}
Expand All @@ -142,4 +142,14 @@ export class SelectDropdownComponent {
);
}
}

public optionAriaLabel(option: SelectOptionComponent | any): string | null {
if (!(option instanceof SelectOptionComponent) || option.disabled) {
return null;
}
const selectLabel = this.selected.has(option.value)
? this.i18n.t("select-dropdown.selected")
: this.i18n.t("select-dropdown.unselected");
return `${option.label}, ${selectLabel}`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
[class.disabled]="option.disabled"
[attr.aria-disabled]="option.disabled"
[attr.aria-selected]="selected.has(option.value)"
[attr.aria-label]="optionAriaLabel(option)"
(mousedown)="handleClickOption($event, option)">

<div class="checkbox" *ngIf="multiple" aria-hidden="true">
<div class="checkbox" *ngIf="multiple">
<i class="fa fa-check" *ngIf="selected.has(option.value)" aria-hidden="true"></i>
</div>
<div class="option-content">
<div class="option-content" aria-hidden="true">
<ng-container *ngIf="select.optionTemplate">
<ng-template [ngTemplateOutlet]="select.optionTemplate" [ngTemplateOutletContext]="{$implicit: option.item}"></ng-template>
</ng-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ select-dropdown:
noMatch: No match
noOptions: No options available
showItems: Showing {num} items
unselectAll: Unselect all
selected: Selected
unselected: Unselected
8 changes: 8 additions & 0 deletions desktop/src/@batch-flask/ui/select/select.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ describe("SelectComponent", () => {
fixture.detectChanges();
expect(labelEl.nativeElement.textContent).toContain("Carrot");
expect(de.nativeElement.getAttribute("aria-label")).toEqual("Myselect: Carrot");
expect(de.nativeElement.title).toEqual("Myselect: Carrot");
});

it("should respect an explicit aria-label attribute", () => {
de.nativeElement.setAttribute("aria-label", "foo");
fixture.detectChanges();
expect(de.nativeElement.getAttribute("aria-label")).toEqual("foo");
expect(de.nativeElement.title).toEqual("Myselect: ");
});

it("list all options when clicking on button", async () => {
Expand Down
Loading

0 comments on commit 732b86b

Please sign in to comment.