Skip to content

Commit c8d2e9e

Browse files
committed
fix: improve PPI widget accessibility
1 parent fa7b48d commit c8d2e9e

2 files changed

Lines changed: 51 additions & 29 deletions

File tree

‎src/app/projects/states/dashboard/directives/task-dashboard/directives/task-description-card/ppi-widget/ppi-widget.component.html‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,47 +7,46 @@
77
}
88
@case ('error') {
99
<div class="ppi-widget ppi-widget--error">
10-
<mat-icon aria-hidden="false" aria-label="Error icon">error</mat-icon>
10+
<mat-icon [attr.aria-hidden]="true">error</mat-icon>
1111
<span>{{ view.message }}</span>
1212
<button mat-button (click)="retry()">Retry</button>
1313
</div>
1414
}
1515
@case ('unavailable') {
1616
<div class="ppi-widget ppi-widget--muted">
17-
<mat-icon aria-hidden="false" aria-label="Info icon">info</mat-icon>
17+
<mat-icon [attr.aria-hidden]="true">info</mat-icon>
1818
<span>{{ view.message }}</span>
1919
</div>
2020
}
2121
@case ('disabled') {
2222
<div class="ppi-widget ppi-widget--muted">
23-
<mat-icon aria-hidden="false" aria-label="Disabled icon">block</mat-icon>
23+
<mat-icon [attr.aria-hidden]="true">block</mat-icon>
2424
<span>{{ view.message }}</span>
2525
</div>
2626
}
2727
@case ('hidden') {
2828
<div class="ppi-widget ppi-widget--muted">
29-
<mat-icon aria-hidden="false" aria-label="Hidden icon">visibility_off</mat-icon>
29+
<mat-icon [attr.aria-hidden]="true">visibility_off</mat-icon>
3030
<span>{{ view.message }}</span>
3131
</div>
3232
}
3333
@case ('no-data') {
3434
<div class="ppi-widget ppi-widget--muted">
35-
<mat-icon aria-hidden="false" aria-label="No data icon">hourglass_empty</mat-icon>
35+
<mat-icon [attr.aria-hidden]="true">hourglass_empty</mat-icon>
3636
<span>No peer submissions yet</span>
3737
</div>
3838
}
3939
@case ('stale') {
4040
<div class="ppi-widget ppi-widget--warning">
41-
<mat-icon aria-hidden="false" aria-label="Outdated data warning">history</mat-icon>
41+
<mat-icon [attr.aria-hidden]="true">history</mat-icon>
4242
<span>{{ view.message }}</span>
4343
<span class="ppi-widget__stale-badge">Data may be outdated</span>
4444
</div>
4545
}
4646
@case ('success') {
4747
<div class="ppi-widget">
48-
<span
49-
>{{ view.data?.submittedPercentage }}% of peers at your target grade have submitted</span
50-
>
48+
<span aria-label="Peer submission progress">{{ view.data?.submittedPercentage }}% of peers at your target grade have submitted</span>
5149
</div>
5250
}
5351
}
52+

‎src/app/projects/states/dashboard/directives/task-dashboard/directives/task-description-card/ppi-widget/ppi-widget.component.spec.ts‎

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import {beforeEach, describe, expect, it, vi} from 'vitest';
2-
import {SimpleChange} from '@angular/core';
3-
import {ComponentFixture, TestBed} from '@angular/core/testing';
4-
import {MatButtonModule} from '@angular/material/button';
5-
import {MatIconModule} from '@angular/material/icon';
6-
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
7-
import {Observable, Subject, of, throwError} from 'rxjs';
8-
import {PeerProgressIndicator} from 'src/app/api/models/peer-progress-indicator';
9-
import {Task} from 'src/app/api/models/task';
10-
import {TaskDefinition} from 'src/app/api/models/task-definition';
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
import { SimpleChange } from '@angular/core';
3+
import { ComponentFixture, TestBed } from '@angular/core/testing';
4+
import { MatButtonModule } from '@angular/material/button';
5+
import { MatIconModule } from '@angular/material/icon';
6+
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
7+
import { Observable, Subject, of, throwError } from 'rxjs';
8+
import { PeerProgressIndicator } from 'src/app/api/models/peer-progress-indicator';
9+
import { Task } from 'src/app/api/models/task';
10+
import { TaskDefinition } from 'src/app/api/models/task-definition';
1111
import {
1212
DISABLED_STATE,
1313
NORMAL_STATE,
@@ -16,24 +16,24 @@ import {
1616
UNAVAILABLE_STATE,
1717
ZERO_PERCENT_STATE,
1818
} from 'src/app/api/services/mock';
19-
import {PeerProgressIndicatorService} from 'src/app/api/services/peer-progress-indicator.service';
20-
import {PpiWidgetComponent} from './ppi-widget.component';
19+
import { PeerProgressIndicatorService } from 'src/app/api/services/peer-progress-indicator.service';
20+
import { PpiWidgetComponent } from './ppi-widget.component';
2121

2222
describe('PpiWidgetComponent', () => {
2323
let component: PpiWidgetComponent;
2424
let fixture: ComponentFixture<PpiWidgetComponent>;
2525
let getIndicator: ReturnType<typeof vi.fn>;
2626

27-
const mockTask = {project: {unit: {id: 1}, targetGrade: 2}} as unknown as Task;
28-
const mockTaskDef = {id: 99} as unknown as TaskDefinition;
27+
const mockTask = { project: { unit: { id: 1 }, targetGrade: 2 } } as unknown as Task;
28+
const mockTaskDef = { id: 99 } as unknown as TaskDefinition;
2929

3030
beforeEach(async () => {
3131
getIndicator = vi.fn();
3232

3333
await TestBed.configureTestingModule({
3434
declarations: [PpiWidgetComponent],
3535
imports: [MatIconModule, MatProgressSpinnerModule, MatButtonModule],
36-
providers: [{provide: PeerProgressIndicatorService, useValue: {getIndicator}}],
36+
providers: [{ provide: PeerProgressIndicatorService, useValue: { getIndicator } }],
3737
}).compileComponents();
3838

3939
fixture = TestBed.createComponent(PpiWidgetComponent);
@@ -44,7 +44,7 @@ describe('PpiWidgetComponent', () => {
4444

4545
function load(response$: Observable<PeerProgressIndicator>) {
4646
getIndicator.mockReturnValue(response$);
47-
component.ngOnChanges({task: new SimpleChange(null, mockTask, true)});
47+
component.ngOnChanges({ task: new SimpleChange(null, mockTask, true) });
4848
fixture.detectChanges();
4949
}
5050

@@ -97,7 +97,7 @@ describe('PpiWidgetComponent', () => {
9797
const subject: Subject<PeerProgressIndicator> = new Subject();
9898
getIndicator.mockReturnValue(subject.asObservable());
9999

100-
component.ngOnChanges({task: new SimpleChange(null, mockTask, true)});
100+
component.ngOnChanges({ task: new SimpleChange(null, mockTask, true) });
101101
expect(component.view.state).toBe('loading');
102102

103103
subject.next(NORMAL_STATE);
@@ -108,7 +108,7 @@ describe('PpiWidgetComponent', () => {
108108
const subject: Subject<PeerProgressIndicator> = new Subject();
109109
getIndicator.mockReturnValue(subject.asObservable());
110110

111-
component.ngOnChanges({task: new SimpleChange(null, mockTask, true)});
111+
component.ngOnChanges({ task: new SimpleChange(null, mockTask, true) });
112112
expect(component.view.state).toBe('loading');
113113

114114
subject.error(new Error('network down'));
@@ -133,13 +133,36 @@ describe('PpiWidgetComponent', () => {
133133
.mockReturnValueOnce(first.asObservable())
134134
.mockReturnValueOnce(second.asObservable());
135135

136-
component.ngOnChanges({task: new SimpleChange(null, mockTask, true)});
137-
component.ngOnChanges({task: new SimpleChange(null, mockTask, true)});
136+
component.ngOnChanges({ task: new SimpleChange(null, mockTask, true) });
137+
component.ngOnChanges({ task: new SimpleChange(null, mockTask, true) });
138138

139139
first.next(NORMAL_STATE);
140140
expect(component.view.state).toBe('loading');
141141

142142
second.next(SUPPRESSED_STATE);
143143
expect(component.view.state).toBe('hidden');
144144
});
145+
146+
it('hides decorative icons from screen readers', () => {
147+
load(throwError(() => new Error('network down')));
148+
fixture.detectChanges();
149+
150+
const icons = fixture.nativeElement.querySelectorAll('mat-icon');
151+
152+
icons.forEach((icon: HTMLElement) => {
153+
expect(icon.getAttribute('aria-hidden')).toBe('true');
154+
});
155+
});
156+
157+
it('provides an accessible name for the peer progress value', () => {
158+
load(of(NORMAL_STATE));
159+
fixture.detectChanges();
160+
161+
const value = fixture.nativeElement.querySelector(
162+
'.ppi-widget:not(.ppi-widget--muted) span[aria-label="Peer submission progress"]',
163+
);
164+
165+
expect(value).toBeTruthy();
166+
expect(value.textContent).toContain('42%');
167+
});
145168
});

0 commit comments

Comments
 (0)