Skip to content

Commit

Permalink
feat(typeahead): localize message for the live element
Browse files Browse the repository at this point in the history
Enable localization for the live element.
Fixes ng-bootstrap#4560
  • Loading branch information
Jeremy CHOISY committed Jan 22, 2024
1 parent af50bd3 commit 9a6d122
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 20 deletions.
2 changes: 2 additions & 0 deletions src/index.ts
@@ -1,3 +1,5 @@
import '@angular/localize/init';

import { NgModule } from '@angular/core';

import { NgbAccordionModule } from './accordion/accordion.module';
Expand Down
82 changes: 63 additions & 19 deletions src/typeahead/typeahead.spec.ts
Expand Up @@ -8,7 +8,7 @@ import { debounceTime, filter, map } from 'rxjs/operators';

import { createGenericTestComponent, triggerEvent } from '../test/common';
import { expectResults, getWindowLinks } from '../test/typeahead/common';
import { ARIA_LIVE_DELAY } from '../util/accessibility/live';
import { ARIA_LIVE_DELAY, Live } from '../util/accessibility/live';
import { NgbTypeahead } from './typeahead';
import { NgbTypeaheadConfig } from './typeahead-config';
import { NgbHighlight } from './highlight';
Expand Down Expand Up @@ -63,9 +63,18 @@ function expectWindowResults(element, expectedResults: string[]) {
}

describe('ngb-typeahead', () => {
let mockLiveService: Partial<Live>;

beforeEach(() => {
mockLiveService = {
say: createSpy('say'),
};

TestBed.configureTestingModule({
providers: [{ provide: ARIA_LIVE_DELAY, useValue: null }],
providers: [
{ provide: ARIA_LIVE_DELAY, useValue: null },
{ provide: Live, useValue: mockLiveService },
],
});
});

Expand Down Expand Up @@ -312,7 +321,7 @@ describe('ngb-typeahead', () => {

it('should use provided result formatter function', () => {
const fixture = createTestComponent(
`<input type="text" [ngbTypeahead]="find" [resultFormatter]="uppercaseFormatter"/>`,
`<input type="text" [ngbTypeahead]="find" [resultFormatter]="uppercaseFormatter"/>`
);
const compiled = fixture.nativeElement;

Expand Down Expand Up @@ -403,7 +412,7 @@ describe('ngb-typeahead', () => {

it('should not select the result on TAB, close window and not write to the input when focusFirst is false', () => {
const fixture = createTestComponent(
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="find" [focusFirst]="false"/>`,
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="find" [focusFirst]="false"/>`
);
const compiled = fixture.nativeElement;

Expand Down Expand Up @@ -432,7 +441,7 @@ describe('ngb-typeahead', () => {

it('should apply additional class when specified', () => {
const fixture = createTestComponent(
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="find" popupClass="test"/>`,
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="find" popupClass="test"/>`
);
const compiled = fixture.nativeElement;

Expand All @@ -446,7 +455,7 @@ describe('ngb-typeahead', () => {

it('should apply additional classes when specified', () => {
const fixture = createTestComponent(
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="find" popupClass="test other"/>`,
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="find" popupClass="test other"/>`
);
const compiled = fixture.nativeElement;

Expand Down Expand Up @@ -723,7 +732,7 @@ describe('ngb-typeahead', () => {

it('should not propagate model when preventDefault() is called on selectEvent', () => {
const fixture = createTestComponent(
'<input [(ngModel)]="model" [ngbTypeahead]="find" (selectItem)="$event.preventDefault()"/>',
'<input [(ngModel)]="model" [ngbTypeahead]="find" (selectItem)="$event.preventDefault()"/>'
);

// clicking selected
Expand Down Expand Up @@ -752,7 +761,7 @@ describe('ngb-typeahead', () => {
const fixture = createTestComponent(
`@if (show) {
<input [ngbTypeahead]="find" container="${selector}"/>
}`,
}`
);

changeInput(fixture.nativeElement, 'one');
Expand Down Expand Up @@ -781,7 +790,7 @@ describe('ngb-typeahead', () => {

it('should have configurable autocomplete attribute', () => {
const fixture = createTestComponent(
'<input type="text" [ngbTypeahead]="findObjects" autocomplete="ignored-123456"/>',
'<input type="text" [ngbTypeahead]="findObjects" autocomplete="ignored-123456"/>'
);
const input = getNativeInput(fixture.nativeElement);

Expand Down Expand Up @@ -838,12 +847,47 @@ describe('ngb-typeahead', () => {
expect(input.getAttribute('aria-owns')).toBeNull();
expect(input.getAttribute('aria-activedescendant')).toBeNull();
}));

describe('live', () => {
let fixture: ComponentFixture<TestComponent>;
let compiled: any;

beforeEach(() => {
fixture = createTestComponent(`<input type="text" [(ngModel)]="model" [ngbTypeahead]="find"/>`);
compiled = fixture.nativeElement;
});

it('should call the method with the correct message when there is more than one result', fakeAsync(() => {
tick();
changeInput(compiled, 'o');
fixture.detectChanges();

expectWindowResults(compiled, ['+one', 'one more']);
expect(mockLiveService.say).toHaveBeenCalledWith('2 results available');
}));

it('should call the method with the correct message when there is not any result available', fakeAsync(() => {
tick();
changeInput(compiled, 'a');
fixture.detectChanges();

expect(mockLiveService.say).toHaveBeenCalledWith('No results available');
}));

it('should call the method with the correct message when there is one single result available', fakeAsync(() => {
tick();
changeInput(compiled, 'one more');
fixture.detectChanges();

expect(mockLiveService.say).toHaveBeenCalledWith('1 result available');
}));
});
});

describe('hint', () => {
it('should show hint when an item starts with user input', fakeAsync(() => {
const fixture = createTestComponent(
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="findAnywhere" [showHint]="true"/>`,
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="findAnywhere" [showHint]="true"/>`
);
const compiled = fixture.nativeElement;
const inputEl = getNativeInput(compiled);
Expand All @@ -867,7 +911,7 @@ describe('ngb-typeahead', () => {

it('should show hint with no selection when an item does not starts with user input', fakeAsync(() => {
const fixture = createTestComponent(
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="findAnywhere" [showHint]="true"/>`,
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="findAnywhere" [showHint]="true"/>`
);
const compiled = fixture.nativeElement;
const inputEl = getNativeInput(compiled);
Expand Down Expand Up @@ -912,7 +956,7 @@ describe('ngb-typeahead', () => {

it('should not show hint when there is no result selected', fakeAsync(() => {
const fixture = createTestComponent(
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="find" [showHint]="true" [focusFirst]="false"/>`,
`<input type="text" [(ngModel)]="model" [ngbTypeahead]="find" [showHint]="true" [focusFirst]="false"/>`
);
fixture.detectChanges();
const compiled = fixture.nativeElement;
Expand Down Expand Up @@ -999,7 +1043,7 @@ describe('ngb-typeahead', () => {
let inputEl;
beforeEach(() => {
fixture = createTestComponent(
`<input type='text' [(ngModel)]='model' [ngbTypeahead]='findAnywhere' [selectOnExact]='true'/>`,
`<input type='text' [(ngModel)]='model' [ngbTypeahead]='findAnywhere' [selectOnExact]='true'/>`
);
compiled = fixture.nativeElement;
inputEl = getNativeInput(compiled);
Expand Down Expand Up @@ -1033,7 +1077,7 @@ describe('ngb-typeahead', () => {
[ngbTypeahead]='findObjectsFormatter'
[selectOnExact]='true'
[inputFormatter]='formatter'
[resultFormatter]='formatter'/>`,
[resultFormatter]='formatter'/>`
);
compiled = fixture.nativeElement;
inputEl = getNativeInput(compiled);
Expand Down Expand Up @@ -1066,7 +1110,7 @@ describe('ngb-typeahead', () => {
`
<form [formGroup]='form'>
<input type='text' formControlName='control' [ngbTypeahead]='findAnywhere' [selectOnExact]='true' [editable]='false'/>
</form>`,
</form>`
);
compiled = fixture.nativeElement;
inputEl = getNativeInput(compiled);
Expand Down Expand Up @@ -1124,15 +1168,15 @@ class TestComponent {
find = (text$: Observable<string>) => {
const clicks$ = this.click$.pipe(filter(() => !this.typeahead.isPopupOpen()));
this.findOutput$ = merge(text$, this.focus$, clicks$).pipe(
map((text) => this._strings.filter((v) => v.startsWith(text))),
map((text) => this._strings.filter((v) => v.startsWith(text)))
);
return this.findOutput$;
};

findFilter = (text$: Observable<string>) => {
return text$.pipe(
filter((term) => term.length > 1),
map((text) => this._strings.filter((v) => v.indexOf(text) > -1)),
map((text) => this._strings.filter((v) => v.indexOf(text) > -1))
);
};

Expand Down Expand Up @@ -1184,7 +1228,7 @@ class TestOnPushComponent {
find = (text$: Observable<string>) => {
return text$.pipe(
debounceTime(200),
map((text) => this._strings.filter((v) => v.startsWith(text))),
map((text) => this._strings.filter((v) => v.startsWith(text)))
);
};
}
Expand All @@ -1196,7 +1240,7 @@ class TestAsyncComponent {
find = (text$: Observable<string>) => {
return text$.pipe(
debounceTime(200),
map((text) => this._strings.filter((v) => v.startsWith(text))),
map((text) => this._strings.filter((v) => v.startsWith(text)))
);
};
}
14 changes: 13 additions & 1 deletion src/typeahead/typeahead.ts
Expand Up @@ -399,6 +399,18 @@ export class NgbTypeahead implements ControlValueAccessor, OnInit, OnChanges, On
this._nativeElement.value = toString(value);
}

private _getAnnounceLocalizedMessage(count: number): string {
if (count === 0) {
return $localize`:@@ngb.typeahead.no-results:No results available`;
}

if (count === 1) {
return $localize`:@@ngb.typeahead.one-result:1 result available`;
}

return $localize`:@@ngb.typeahead.many-results:${count}:count: results available`;
}

private _subscribeToUserInput(): void {
const results$ = this._valueChanges$.pipe(
tap((value) => {
Expand Down Expand Up @@ -445,7 +457,7 @@ export class NgbTypeahead implements ControlValueAccessor, OnInit, OnChanges, On

// live announcer
const count = results ? results.length : 0;
this._live.say(count === 0 ? 'No results available' : `${count} result${count === 1 ? '' : 's'} available`);
this._live.say(this._getAnnounceLocalizedMessage(count));
});
}

Expand Down

0 comments on commit 9a6d122

Please sign in to comment.