From 995f13e4b35c07eab2b17da9ac9326f23811043b Mon Sep 17 00:00:00 2001 From: Daniel Shuy <17351764+daniel-shuy@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:59:05 +0800 Subject: [PATCH] angular-material: Translate enum for AutocompleteControlRenderer --- .../library/controls/autocomplete.renderer.ts | 64 +++++++++++++++---- .../test/autocomplete-control.spec.ts | 54 ++++++++++++++-- 2 files changed, 99 insertions(+), 19 deletions(-) diff --git a/packages/angular-material/src/library/controls/autocomplete.renderer.ts b/packages/angular-material/src/library/controls/autocomplete.renderer.ts index 596af746f..a138820d7 100644 --- a/packages/angular-material/src/library/controls/autocomplete.renderer.ts +++ b/packages/angular-material/src/library/controls/autocomplete.renderer.ts @@ -34,10 +34,15 @@ import { Actions, composeWithUi, ControlElement, + EnumOption, isEnumControl, + JsonFormsState, + mapStateToEnumControlProps, OwnPropsOfControl, + OwnPropsOfEnum, RankedTester, rankWith, + StatePropsOfControl, } from '@jsonforms/core'; import type { Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; @@ -89,12 +94,13 @@ import { MatAutocompleteModule } from '@angular/material/autocomplete'; autoActiveFirstOption #auto="matAutocomplete" (optionSelected)="onSelect($event)" + [displayWith]="displayFn" > - {{ option }} + {{ option.label }} {{ @@ -127,14 +133,22 @@ export class AutocompleteControlRenderer extends JsonFormsControl implements OnInit { - @Input() options: string[]; - filteredOptions: Observable; + @Input() options?: EnumOption[] | string[]; + translatedOptions?: EnumOption[]; + filteredOptions: Observable; shouldFilter: boolean; focused = false; constructor(jsonformsService: JsonFormsAngularService) { super(jsonformsService); } + + protected mapToProps( + state: JsonFormsState + ): StatePropsOfControl & OwnPropsOfEnum { + return mapStateToEnumControlProps(state, this.getOwnProps()); + } + getEventValue = (event: any) => event.target.value; ngOnInit() { @@ -146,6 +160,10 @@ export class AutocompleteControlRenderer ); } + mapAdditionalProps(_props: StatePropsOfControl & OwnPropsOfEnum) { + this.translatedOptions = _props.options; + } + updateFilter(event: any) { // ENTER if (event.keyCode === 13) { @@ -164,24 +182,44 @@ export class AutocompleteControlRenderer this.triggerValidation(); } - filter(val: string): string[] { - return (this.options || this.scopedSchema.enum || []).filter( + displayFn(option: EnumOption): string { + return option.label; + } + + filter(val: string): EnumOption[] { + return (this.translatedOptions || []).filter( (option) => !this.shouldFilter || !val || - option.toLowerCase().indexOf(val.toLowerCase()) === 0 + option.label.toLowerCase().indexOf(val.toLowerCase()) === 0 ); } - protected getOwnProps(): OwnPropsOfAutoComplete { + protected getOwnProps(): OwnPropsOfControl & OwnPropsOfEnum { return { ...super.getOwnProps(), - options: this.options, + options: this.stringOptionsToEnumOptions(this.options), }; } -} -export const enumControlTester: RankedTester = rankWith(2, isEnumControl); + /** + * For {@link options} input backwards compatibility + */ + protected stringOptionsToEnumOptions( + options: typeof this.options + ): EnumOption[] | undefined { + if (!options) { + return undefined; + } -interface OwnPropsOfAutoComplete extends OwnPropsOfControl { - options: string[]; + return options.every((item) => typeof item === 'string') + ? options.map((str) => { + return { + label: str, + value: str, + } satisfies EnumOption; + }) + : options; + } } + +export const enumControlTester: RankedTester = rankWith(2, isEnumControl); diff --git a/packages/angular-material/test/autocomplete-control.spec.ts b/packages/angular-material/test/autocomplete-control.spec.ts index 53a0bd645..5fd2acc54 100644 --- a/packages/angular-material/test/autocomplete-control.spec.ts +++ b/packages/angular-material/test/autocomplete-control.spec.ts @@ -44,7 +44,12 @@ import { setupMockStore, getJsonFormsService, } from './common'; -import { ControlElement, JsonSchema, Actions } from '@jsonforms/core'; +import { + ControlElement, + JsonSchema, + Actions, + JsonFormsCore, +} from '@jsonforms/core'; import { AutocompleteControlRenderer } from '../src'; import { JsonFormsAngularService } from '@jsonforms/angular'; import { ErrorObject } from 'ajv'; @@ -108,7 +113,6 @@ describe('Autocomplete control Base Tests', () => { fixture.detectChanges(); tick(); expect(component.data).toBe('A'); - expect(inputElement.value).toBe('A'); expect(inputElement.disabled).toBe(false); })); @@ -124,7 +128,6 @@ describe('Autocomplete control Base Tests', () => { tick(); fixture.detectChanges(); expect(component.data).toBe('B'); - expect(inputElement.value).toBe('B'); })); it('should update with undefined value', () => { @@ -140,7 +143,6 @@ describe('Autocomplete control Base Tests', () => { ); fixture.detectChanges(); expect(component.data).toBe(undefined); - expect(inputElement.value).toBe(''); }); it('should update with null value', () => { setupMockStore(fixture, { uischema, schema, data }); @@ -155,7 +157,6 @@ describe('Autocomplete control Base Tests', () => { ); fixture.detectChanges(); expect(component.data).toBe(null); - expect(inputElement.value).toBe(''); }); it('should not update with wrong ref', fakeAsync(() => { setupMockStore(fixture, { uischema, schema, data }); @@ -170,7 +171,6 @@ describe('Autocomplete control Base Tests', () => { fixture.detectChanges(); tick(); expect(component.data).toBe('A'); - expect(inputElement.value).toBe('A'); })); // store needed as we evaluate the calculated enabled value to disable/enable the control it('can be disabled', () => { @@ -275,6 +275,48 @@ describe('AutoComplete control Input Event Tests', () => { .args[0] as MatAutocompleteSelectedEvent; expect(event.option.value).toBe('Y'); })); + it('should render translated enum correctly', fakeAsync(async () => { + setupMockStore(fixture, { uischema, schema, data }); + const state: JsonFormsCore = { + data, + schema, + uischema, + }; + getJsonFormsService(component).init({ + core: state, + i18n: { + translate: (key, defaultMessage) => { + const translations: { [key: string]: string } = { + 'foo.A': 'Translated A', + 'foo.B': 'Translated B', + 'foo.C': 'Translated C', + }; + return translations[key] ?? defaultMessage; + }, + }, + }); + getJsonFormsService(component).updateCore( + Actions.init(data, schema, uischema) + ); + component.ngOnInit(); + fixture.detectChanges(); + const spy = spyOn(component, 'onSelect'); + + await (await loader.getHarness(MatAutocompleteHarness)).focus(); + fixture.detectChanges(); + + await ( + await loader.getHarness(MatAutocompleteHarness) + ).selectOption({ + text: 'Translated B', + }); + fixture.detectChanges(); + tick(); + + const event = spy.calls.mostRecent() + .args[0] as MatAutocompleteSelectedEvent; + expect(event.option.value).toBe('B'); + })); }); describe('AutoComplete control Error Tests', () => { let fixture: ComponentFixture;