Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -67,12 +72,13 @@ import { MatAutocompleteModule } from '@angular/material/autocomplete';
autoActiveFirstOption
#auto="matAutocomplete"
(optionSelected)="onSelect($event)"
[displayWith]="displayFn"
>
<mat-option
*ngFor="let option of filteredOptions | async"
[value]="option"
>
{{ option }}
{{ option.label }}
</mat-option>
</mat-autocomplete>
<mat-hint *ngIf="shouldShowUnfocusedDescription() || focused">{{
Expand Down Expand Up @@ -105,14 +111,22 @@ export class AutocompleteControlRenderer
extends JsonFormsControl
implements OnInit
{
@Input() options: string[];
filteredOptions: Observable<string[]>;
@Input() options?: EnumOption[] | string[];
translatedOptions?: EnumOption[];
filteredOptions: Observable<EnumOption[]>;
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() {
Expand All @@ -124,6 +138,10 @@ export class AutocompleteControlRenderer
);
}

mapAdditionalProps(_props: StatePropsOfControl & OwnPropsOfEnum) {
this.translatedOptions = _props.options;
}

updateFilter(event: any) {
// ENTER
if (event.keyCode === 13) {
Expand All @@ -136,30 +154,49 @@ export class AutocompleteControlRenderer
onSelect(ev: MatAutocompleteSelectedEvent) {
const path = composeWithUi(this.uischema as ControlElement, this.path);
this.shouldFilter = false;
this.jsonFormsService.updateCore(
Actions.update(path, () => ev.option.value)
);
const option: EnumOption = ev.option.value;
this.jsonFormsService.updateCore(Actions.update(path, () => option.value));
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);
118 changes: 108 additions & 10 deletions packages/angular-material/test/autocomplete-control.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,26 @@ import {
setupMockStore,
getJsonFormsService,
} from './common';
import { ControlElement, JsonSchema, Actions } from '@jsonforms/core';
import {
ControlElement,
JsonSchema,
Actions,
JsonFormsCore,
EnumOption,
} from '@jsonforms/core';
import { AutocompleteControlRenderer } from '../src';
import { JsonFormsAngularService } from '@jsonforms/angular';
import { ErrorObject } from 'ajv';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatAutocompleteHarness } from '@angular/material/autocomplete/testing';

const data = { foo: 'A' };
const data = {
foo: {
label: 'A',
value: 'a',
},
};
const schema: JsonSchema = {
type: 'object',
properties: {
Expand Down Expand Up @@ -107,7 +118,10 @@ describe('Autocomplete control Base Tests', () => {
component.ngOnInit();
fixture.detectChanges();
tick();
expect(component.data).toBe('A');
expect(component.data).toEqual({
label: 'A',
value: 'a',
});
expect(inputElement.value).toBe('A');
expect(inputElement.disabled).toBe(false);
}));
Expand All @@ -120,10 +134,20 @@ describe('Autocomplete control Base Tests', () => {
component.ngOnInit();
fixture.detectChanges();
tick();
getJsonFormsService(component).updateCore(Actions.update('foo', () => 'B'));
getJsonFormsService(component).updateCore(
Actions.update('foo', () => {
return {
label: 'B',
value: 'b',
} satisfies EnumOption;
})
);
tick();
fixture.detectChanges();
expect(component.data).toBe('B');
expect(component.data).toEqual({
label: 'B',
value: 'b',
} satisfies EnumOption);
expect(inputElement.value).toBe('B');
}));

Expand Down Expand Up @@ -165,11 +189,28 @@ describe('Autocomplete control Base Tests', () => {
component.ngOnInit();
fixture.detectChanges();
tick();
getJsonFormsService(component).updateCore(Actions.update('foo', () => 'A'));
getJsonFormsService(component).updateCore(Actions.update('bar', () => 'B'));
getJsonFormsService(component).updateCore(
Actions.update('foo', () => {
return {
label: 'A',
value: 'a',
} satisfies EnumOption;
})
);
getJsonFormsService(component).updateCore(
Actions.update('bar', () => {
return {
label: 'B',
value: 'b',
} satisfies EnumOption;
})
);
fixture.detectChanges();
tick();
expect(component.data).toBe('A');
expect(component.data).toEqual({
label: 'A',
value: 'a',
} satisfies EnumOption);
expect(inputElement.value).toBe('A');
}));
// store needed as we evaluate the calculated enabled value to disable/enable the control
Expand Down Expand Up @@ -213,6 +254,7 @@ describe('AutoComplete control Input Event Tests', () => {
let fixture: ComponentFixture<AutocompleteControlRenderer>;
let component: AutocompleteControlRenderer;
let loader: HarnessLoader;
let inputElement: HTMLInputElement;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [componentUT, ...imports],
Expand All @@ -223,6 +265,8 @@ describe('AutoComplete control Input Event Tests', () => {
fixture = TestBed.createComponent(componentUT);
component = fixture.componentInstance;
loader = TestbedHarnessEnvironment.loader(fixture);

inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
}));

it('should update via input event', fakeAsync(async () => {
Expand All @@ -249,7 +293,11 @@ describe('AutoComplete control Input Event Tests', () => {
const event = spy.calls.mostRecent()
.args[0] as MatAutocompleteSelectedEvent;

expect(event.option.value).toBe('B');
expect(event.option.value).toEqual({
label: 'B',
value: 'B',
} satisfies EnumOption);
expect(inputElement.value).toBe('B');
}));
it('options should prefer own props', fakeAsync(async () => {
setupMockStore(fixture, { uischema, schema, data });
Expand All @@ -273,7 +321,57 @@ describe('AutoComplete control Input Event Tests', () => {

const event = spy.calls.mostRecent()
.args[0] as MatAutocompleteSelectedEvent;
expect(event.option.value).toBe('Y');
expect(event.option.value).toEqual({
label: 'Y',
value: 'Y',
} satisfies EnumOption);
expect(inputElement.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).toEqual({
label: 'Translated B',
value: 'B',
} satisfies EnumOption);
expect(inputElement.value).toBe('Translated B');
}));
});
describe('AutoComplete control Error Tests', () => {
Expand Down
Loading
Loading