Skip to content

Commit 263598a

Browse files
authored
Merge pull request #2571 from intersective/2.4.y.z/CORE-8055/search-box-where-appropriate
[CORE-8055] search (review assesment + activity at home page)
2 parents 7d72b54 + 0dab791 commit 263598a

11 files changed

Lines changed: 797 additions & 60 deletions

projects/v3/src/app/components/list-item/list-item.component.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
<ion-item [id]="id"
22
[ngClass]="{'active': active, 'hasCallToActionBtn': callToActionBtn}"
33
[lines]="lines"
4-
tabindex="0"
5-
role="listitem">
4+
[button]="button"
5+
[attr.tabindex]="button ? 0 : null"
6+
[attr.role]="itemRole || (button ? 'button' : 'listitem')"
7+
[attr.aria-selected]="ariaSelected === undefined ? null : (ariaSelected ? 'true' : 'false')"
8+
[attr.aria-current]="ariaCurrent || null">
69
<ng-container *ngIf="!loading; else loadingSkeleton">
710
<div class="icon-container activitypage" *ngIf="!leadImage">
811
<ion-icon *ngIf="leadingIcon"

projects/v3/src/app/components/list-item/list-item.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ export class ListItemComponent {
4949

5050
// used if there are ending action buttons
5151
@Input() endingActionBtnIcons: string[];
52+
@Input() itemRole: string = 'listitem';
53+
@Input() ariaSelected?: boolean;
54+
@Input() ariaCurrent?: string;
55+
@Input() button = false;
5256
// named as "any" to support any callback parameter format
5357
@Output() anyBtnClick = new EventEmitter<any>();
5458
@Output() actionBtnClick = new EventEmitter<number>();

projects/v3/src/app/components/review-list/review-list.component.html

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,23 @@
1-
<ion-segment (ionChange)="switchStatus()" value="pending">
1+
<div class="sr-only" [id]="searchLabelId" i18n>Search reviews</div>
2+
<div class="sr-only" [id]="searchHintId" i18n>Type a review name to filter the list.</div>
3+
4+
<ion-searchbar
5+
class="review-search"
6+
[value]="searchTerm"
7+
(ionInput)="onSearchTermChange($event.detail.value)"
8+
[debounce]="200"
9+
[attr.aria-labelledby]="searchLabelId"
10+
[attr.aria-describedby]="searchHintId"
11+
i18n-placeholder
12+
placeholder="Search reviews"
13+
></ion-searchbar>
14+
15+
<ion-segment
16+
(ionChange)="switchStatus($event)"
17+
[value]="segmentValue"
18+
[attr.aria-controls]="listId"
19+
aria-label="Filter reviews by status" i18n-aria-label
20+
>
221
<ion-segment-button value="pending" mode="ios">
322
<ion-label i18n>Pending</ion-label>
423
</ion-segment-button>
@@ -7,35 +26,51 @@
726
</ion-segment-button>
827
</ion-segment>
928

10-
<ion-list *ngIf="reviews">
11-
<ng-container *ngFor="let review of reviews">
29+
<h2 class="sr-only" [id]="listLabelId" i18n>Review list</h2>
30+
31+
<ion-list *ngIf="reviews"
32+
[attr.id]="listId"
33+
role="list"
34+
[attr.aria-labelledby]="listLabelId"
35+
>
36+
<ng-container *ngFor="let review of filteredReviews; trackBy: trackBySubmission">
1237
<app-list-item
38+
#reviewItem
1339
class="focusable"
14-
*ngIf="review.isDone === showDone"
40+
[id]="'review-' + review.submissionId"
1541
[title]="review.name"
1642
[subtitle1]="review.submitterName"
1743
subtitle1Color="grey-75"
1844
[subtitle2]="review.teamName"
1945
subtitle2Color="grey-75"
2046
leadingIcon="eye"
2147
[active]="currentReview && currentReview.submissionId === review.submissionId"
48+
[button]="true"
49+
[ariaSelected]="currentReview && currentReview.submissionId === review.submissionId"
50+
[ariaCurrent]="currentReview && currentReview.submissionId === review.submissionId ? 'true' : null"
2251
lines="full"
2352
(click)="goto(review)"
2453
(keydown)="goto(review, $event)"
2554
[endingText]="review.date"
2655
endingTextColor="grey-75"
27-
tabindex="0"
28-
role="button"
2956
></app-list-item>
3057
</ng-container>
3158
</ion-list>
3259

33-
<ion-list *ngIf="reviews === null">
60+
<p *ngIf="resultsAnnouncement" class="sr-only" aria-live="polite">{{ resultsAnnouncement }}</p>
61+
62+
<ion-list *ngIf="reviews === null" role="status" aria-live="polite">
3463
<app-list-item [loading]="true"></app-list-item>
3564
<app-list-item [loading]="true"></app-list-item>
3665
</ion-list>
3766

38-
<div *ngIf="noReviews" class="ion-text-center no-review">
67+
<div *ngIf="hasSearchWithoutResults" class="ion-text-center no-review" role="status" aria-live="polite">
68+
<ion-icon class="large-icon" color="primary" name="search-outline"></ion-icon>
69+
<p class="body-1" i18n>No reviews match your search.</p>
70+
<ion-text class="subtitle-2" color="grey-75" i18n>Try a different search term to find a review.</ion-text>
71+
</div>
72+
73+
<div *ngIf="!hasSearchWithoutResults && noReviews" class="ion-text-center no-review" role="status" aria-live="polite">
3974
<ion-icon class="large-icon" color="primary" name="chatbox-ellipses-outline"></ion-icon>
4075
<p class="body-1" i18n="Sample: You have no pending/completed review yet!">You have no {{ noReviews }} review yet!</p>
4176
<ion-text class="subtitle-2" color="grey-75" i18n>Reviews show up here, so you can easily view them here later.</ion-text>

projects/v3/src/app/components/review-list/review-list.component.scss

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,28 @@ ion-segment {
1212
border: 1px solid var(--ion-color-primary);
1313
}
1414
}
15-
.focusable:focus {
16-
border: 1px solid var(--ion-color-primary);
17-
display: block;
15+
16+
.review-search {
17+
padding: 0;
18+
}
19+
.sr-only {
20+
border: 0;
21+
clip: rect(0 0 0 0);
22+
clip-path: inset(50%);
23+
height: 1px;
24+
margin: -1px;
25+
overflow: hidden;
26+
padding: 0;
27+
position: absolute;
28+
width: 1px;
29+
white-space: nowrap;
30+
}
31+
32+
.focusable ::ng-deep ion-item:focus-visible {
33+
outline: 2px solid var(--ion-color-primary);
34+
outline-offset: 2px;
35+
}
36+
37+
.focusable ::ng-deep ion-item.active {
38+
border-left: 4px solid var(--ion-color-primary);
1839
}

projects/v3/src/app/components/review-list/review-list.component.spec.ts

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2+
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange } from '@angular/core';
3+
import { FormsModule } from '@angular/forms';
24
import { IonicModule } from '@ionic/angular';
35

46
import { ReviewListComponent } from './review-list.component';
@@ -10,7 +12,8 @@ describe('ReviewListComponent', () => {
1012
beforeEach(waitForAsync(() => {
1113
TestBed.configureTestingModule({
1214
declarations: [ ReviewListComponent ],
13-
imports: [IonicModule.forRoot()]
15+
imports: [IonicModule.forRoot(), FormsModule],
16+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
1417
}).compileComponents();
1518

1619
fixture = TestBed.createComponent(ReviewListComponent);
@@ -52,15 +55,26 @@ describe('ReviewListComponent', () => {
5255

5356
describe('switchStatus()', () => {
5457
it('should switch status', () => {
55-
component.reviews = [{
56-
isDone: true,
57-
} as any];
58-
component.showDone = false;
58+
component.reviews = [
59+
{ isDone: false, name: 'Pending review', submissionId: 1 } as any,
60+
{ isDone: true, name: 'Completed review', submissionId: 2 } as any,
61+
];
62+
component.currentReview = component.reviews[0];
63+
component.ngOnChanges({
64+
reviews: new SimpleChange(null, component.reviews, true),
65+
currentReview: new SimpleChange(null, component.currentReview, true),
66+
});
5967
component.goToFirstOnSwitch = true;
6068
const spy = spyOn(component.navigate, 'emit');
61-
component.switchStatus();
62-
expect(spy).toHaveBeenCalled();
69+
component.switchStatus({
70+
detail: {
71+
value: 'completed',
72+
},
73+
} as any);
74+
expect(spy).toHaveBeenCalledWith(component.reviews[1]);
6375
expect(component.showDone).toBeTrue();
76+
expect(component.segmentValue).toBe('completed');
77+
expect(component.resultsAnnouncement).toContain('completed');
6478
});
6579
});
6680

@@ -73,6 +87,7 @@ describe('ReviewListComponent', () => {
7387
component.reviews = [{
7488
isDone: true,
7589
} as any];
90+
component.ngOnChanges({ reviews: new SimpleChange(null, component.reviews, false) });
7691
expect(component.noReviews).toEqual('');
7792
});
7893

@@ -81,6 +96,7 @@ describe('ReviewListComponent', () => {
8196
{ isDone: false } as any
8297
];
8398
component.showDone = true;
99+
component.ngOnChanges({ reviews: new SimpleChange(null, component.reviews, false) });
84100
expect(component.noReviews).toEqual('completed');
85101
});
86102

@@ -89,7 +105,37 @@ describe('ReviewListComponent', () => {
89105
{ isDone: true } as any
90106
];
91107
component.showDone = false;
108+
component.ngOnChanges({ reviews: new SimpleChange(null, component.reviews, false) });
92109
expect(component.noReviews).toEqual('pending');
93110
});
111+
112+
it('should hide default message when searching', () => {
113+
component.reviews = [
114+
{ isDone: true, name: 'Completed review', submissionId: 2 } as any,
115+
];
116+
component.showDone = true;
117+
component.ngOnChanges({ reviews: new SimpleChange(null, component.reviews, false) });
118+
component.onSearchTermChange('');
119+
component.onSearchTermChange('missing');
120+
expect(component.noReviews).toEqual('');
121+
expect(component.hasSearchWithoutResults).toBeTrue();
122+
expect(component.resultsAnnouncement).toContain('No');
123+
});
124+
});
125+
126+
describe('onSearchTermChange()', () => {
127+
it('should filter reviews by title', () => {
128+
component.reviews = [
129+
{ isDone: false, name: 'First review', submissionId: 1 } as any,
130+
{ isDone: false, name: 'Second', submissionId: 2 } as any,
131+
];
132+
component.showDone = false;
133+
component.ngOnChanges({ reviews: new SimpleChange(null, component.reviews, false) });
134+
component.onSearchTermChange('');
135+
component.onSearchTermChange('second');
136+
expect(component.filteredReviews.length).toBe(1);
137+
expect(component.filteredReviews[0].name).toBe('Second');
138+
expect(component.resultsAnnouncement).toContain('1');
139+
});
94140
});
95141
});

0 commit comments

Comments
 (0)