diff --git a/packages/angular-material/.eslintrc.js b/packages/angular-material/.eslintrc.js
index 2b67856c2..486c568b6 100644
--- a/packages/angular-material/.eslintrc.js
+++ b/packages/angular-material/.eslintrc.js
@@ -9,45 +9,54 @@ module.exports = {
},
// There is no file include in ESLint. Thus, ignore all and include files via negative ignore (!)
ignorePatterns: ['/*', '!/src', '!/test', '!/example', '/example/dist'],
- extends: [
- 'eslint:recommended',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:import/recommended',
- 'plugin:import/typescript',
- 'plugin:@angular-eslint/recommended',
- 'plugin:@angular-eslint/template/process-inline-templates',
- 'plugin:prettier/recommended',
- ],
- rules: {
- '@angular-eslint/component-class-suffix': 'off',
- '@angular-eslint/directive-class-suffix': 'off',
- '@angular-eslint/no-conflicting-lifecycle': 'warn',
- '@typescript-eslint/no-explicit-any': 'off',
- // Base rule must be disabled to avoid incorrect errors
- 'no-unused-vars': 'off',
- '@typescript-eslint/no-unused-vars': [
- 'warn', // or "error"
- {
- argsIgnorePattern: '^_',
- varsIgnorePattern: '^_',
- caughtErrorsIgnorePattern: '^_',
- },
- ],
- // workaround for
- // https://github.com/import-js/eslint-plugin-import/issues/1810:
- 'import/no-unresolved': [
- 'error',
- {
- ignore: [
- '@angular/cdk/.*',
- '@angular/core/.*',
- '@angular/material/.*',
- '@angular/platform-browser/.*',
- '@angular/platform-browser-dynamic/.*',
- 'core-js/es7/.*',
- 'zone.js/.*',
+ overrides: [
+ {
+ files: ['*.ts'],
+ extends: [
+ 'eslint:recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:import/recommended',
+ 'plugin:import/typescript',
+ 'plugin:@angular-eslint/recommended',
+ 'plugin:@angular-eslint/template/process-inline-templates',
+ 'plugin:prettier/recommended',
+ ],
+ rules: {
+ '@angular-eslint/component-class-suffix': 'off',
+ '@angular-eslint/directive-class-suffix': 'off',
+ '@angular-eslint/no-conflicting-lifecycle': 'warn',
+ '@typescript-eslint/no-explicit-any': 'off',
+ // Base rule must be disabled to avoid incorrect errors
+ 'no-unused-vars': 'off',
+ '@typescript-eslint/no-unused-vars': [
+ 'warn', // or "error"
+ {
+ argsIgnorePattern: '^_',
+ varsIgnorePattern: '^_',
+ caughtErrorsIgnorePattern: '^_',
+ },
+ ],
+ // workaround for
+ // https://github.com/import-js/eslint-plugin-import/issues/1810:
+ 'import/no-unresolved': [
+ 'error',
+ {
+ ignore: [
+ '@angular/cdk/.*',
+ '@angular/core/.*',
+ '@angular/material/.*',
+ '@angular/platform-browser/.*',
+ '@angular/platform-browser-dynamic/.*',
+ 'core-js/es7/.*',
+ 'zone.js/.*',
+ ],
+ },
],
},
- ],
- },
+ },
+ {
+ files: '*.html',
+ extends: ['plugin:@angular-eslint/template/recommended'],
+ }
+ ],
};
diff --git a/packages/angular-material/src/library/controls/enum.renderer.html b/packages/angular-material/src/library/controls/enum.renderer.html
new file mode 100644
index 000000000..756c341c0
--- /dev/null
+++ b/packages/angular-material/src/library/controls/enum.renderer.html
@@ -0,0 +1,30 @@
+
+ {{ label }}
+
+
+ @for (option of filteredOptions | async; track option.value) {
+
+ {{ option.label }}
+
+ }
+
+ {{
+ description
+ }}
+ {{ error }}
+
diff --git a/packages/angular-material/src/library/controls/enum.renderer.scss b/packages/angular-material/src/library/controls/enum.renderer.scss
new file mode 100644
index 000000000..9da6d18ac
--- /dev/null
+++ b/packages/angular-material/src/library/controls/enum.renderer.scss
@@ -0,0 +1,7 @@
+:host {
+ display: flex;
+ flex-direction: row;
+}
+mat-form-field {
+ flex: 1 1 auto;
+}
diff --git a/packages/angular-material/src/library/controls/autocomplete.renderer.ts b/packages/angular-material/src/library/controls/enum.renderer.ts
similarity index 74%
rename from packages/angular-material/src/library/controls/autocomplete.renderer.ts
rename to packages/angular-material/src/library/controls/enum.renderer.ts
index 32e19437e..b5de1b13a 100644
--- a/packages/angular-material/src/library/controls/autocomplete.renderer.ts
+++ b/packages/angular-material/src/library/controls/enum.renderer.ts
@@ -36,8 +36,10 @@ import {
ControlElement,
EnumOption,
isEnumControl,
+ isOneOfEnumControl,
JsonFormsState,
mapStateToEnumControlProps,
+ mapStateToOneOfEnumControlProps,
OwnPropsOfControl,
OwnPropsOfEnum,
RankedTester,
@@ -53,50 +55,9 @@ import { MatInputModule } from '@angular/material/input';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
@Component({
- selector: 'AutocompleteControlRenderer',
- template: `
-
- {{ label }}
-
-
- @for (option of filteredOptions | async; track option.value) {
-
- {{ option.label }}
-
- }
-
- {{
- description
- }}
- {{ error }}
-
- `,
- styles: [
- `
- :host {
- display: flex;
- flex-direction: row;
- }
- mat-form-field {
- flex: 1 1 auto;
- }
- `,
- ],
+ selector: 'OneOfEnumControlRenderer',
+ templateUrl: './enum.renderer.html',
+ styleUrls: ['./enum.renderer.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
CommonModule,
@@ -106,11 +67,11 @@ import { MatAutocompleteModule } from '@angular/material/autocomplete';
MatAutocompleteModule,
],
})
-export class AutocompleteControlRenderer
+export class OneOfEnumControlRenderer
extends JsonFormsControl
implements OnInit
{
- @Input() options?: EnumOption[] | string[];
+ @Input() options?: EnumOption[];
valuesToTranslatedOptions?: Map;
filteredOptions: Observable;
shouldFilter: boolean;
@@ -123,7 +84,7 @@ export class AutocompleteControlRenderer
protected override mapToProps(
state: JsonFormsState
): StatePropsOfControl & OwnPropsOfEnum {
- return mapStateToEnumControlProps(state, this.getOwnProps());
+ return mapStateToOneOfEnumControlProps(state, this.getOwnProps());
}
getEventValue = (event: any) => event.target.value;
@@ -209,29 +170,51 @@ export class AutocompleteControlRenderer
protected getOwnProps(): OwnPropsOfControl & OwnPropsOfEnum {
return {
...super.getOwnProps(),
- options: this.stringOptionsToEnumOptions(this.options),
+ options: this.options,
};
}
+}
- /**
- * For {@link options} input backwards compatibility
- */
- protected stringOptionsToEnumOptions(
- options: typeof this.options
- ): EnumOption[] | undefined {
- if (!options) {
- return undefined;
- }
+export const oneOfEnumControlTester: RankedTester = rankWith(
+ 5,
+ isOneOfEnumControl
+);
- return options.every((item) => typeof item === 'string')
- ? options.map((str) => {
- return {
- label: str,
- value: str,
- } satisfies EnumOption;
- })
- : options;
+@Component({
+ selector: 'EnumControlRenderer, AutocompleteControlRenderer',
+ templateUrl: './enum.renderer.html',
+ styleUrls: ['./enum.renderer.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [
+ CommonModule,
+ ReactiveFormsModule,
+ MatFormFieldModule,
+ MatInputModule,
+ MatAutocompleteModule,
+ ],
+})
+export class EnumControlRenderer extends OneOfEnumControlRenderer {
+ // eslint-disable-next-line @angular-eslint/no-input-rename
+ @Input('options')
+ set stringOptions(strOptions: string[]) {
+ this.options = strOptions.map((str) => {
+ return {
+ label: str,
+ value: str,
+ };
+ });
+ }
+
+ protected override mapToProps(
+ state: JsonFormsState
+ ): StatePropsOfControl & OwnPropsOfEnum {
+ return mapStateToEnumControlProps(state, this.getOwnProps());
}
}
+/**
+ * For {@link AutocompleteControlRenderer} class name backwards compatibility
+ */
+export { EnumControlRenderer as AutocompleteControlRenderer };
+
export const enumControlTester: RankedTester = rankWith(2, isEnumControl);
diff --git a/packages/angular-material/src/library/controls/index.ts b/packages/angular-material/src/library/controls/index.ts
index ec7666823..2a721313d 100644
--- a/packages/angular-material/src/library/controls/index.ts
+++ b/packages/angular-material/src/library/controls/index.ts
@@ -29,4 +29,4 @@ export * from './number.renderer';
export * from './range.renderer';
export * from './date.renderer';
export * from './toggle.renderer';
-export * from './autocomplete.renderer';
+export * from './enum.renderer';
diff --git a/packages/angular-material/src/library/index.ts b/packages/angular-material/src/library/index.ts
index 64c286e7d..2a32c6c2d 100644
--- a/packages/angular-material/src/library/index.ts
+++ b/packages/angular-material/src/library/index.ts
@@ -54,9 +54,11 @@ import {
ToggleControlRendererTester,
} from './controls/toggle.renderer';
import {
- AutocompleteControlRenderer,
+ EnumControlRenderer,
enumControlTester,
-} from './controls/autocomplete.renderer';
+ OneOfEnumControlRenderer,
+ oneOfEnumControlTester,
+} from './controls/enum.renderer';
import {
ObjectControlRenderer,
ObjectControlRendererTester,
@@ -106,7 +108,8 @@ export const angularMaterialRenderers: {
{ tester: RangeControlRendererTester, renderer: RangeControlRenderer },
{ tester: DateControlRendererTester, renderer: DateControlRenderer },
{ tester: ToggleControlRendererTester, renderer: ToggleControlRenderer },
- { tester: enumControlTester, renderer: AutocompleteControlRenderer },
+ { tester: enumControlTester, renderer: EnumControlRenderer },
+ { tester: oneOfEnumControlTester, renderer: OneOfEnumControlRenderer },
{ tester: ObjectControlRendererTester, renderer: ObjectControlRenderer },
// layouts
{ tester: verticalLayoutTester, renderer: VerticalLayoutRenderer },
diff --git a/packages/angular-material/src/library/module.ts b/packages/angular-material/src/library/module.ts
index bee96ef8f..f286658e1 100644
--- a/packages/angular-material/src/library/module.ts
+++ b/packages/angular-material/src/library/module.ts
@@ -45,9 +45,12 @@ import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { JsonFormsModule } from '@jsonforms/angular';
-import { AutocompleteControlRenderer } from './controls/autocomplete.renderer';
import { BooleanControlRenderer } from './controls/boolean.renderer';
import { DateControlRenderer } from './controls/date.renderer';
+import {
+ EnumControlRenderer,
+ OneOfEnumControlRenderer,
+} from './controls/enum.renderer';
import { NumberControlRenderer } from './controls/number.renderer';
import { RangeControlRenderer } from './controls/range.renderer';
import { TextAreaRenderer } from './controls/textarea.renderer';
@@ -104,7 +107,8 @@ import { LayoutChildrenRenderPropsPipe } from './layouts';
MasterListComponent,
JsonFormsDetailComponent,
ObjectControlRenderer,
- AutocompleteControlRenderer,
+ EnumControlRenderer,
+ OneOfEnumControlRenderer,
TableRenderer,
ArrayLayoutRenderer,
LayoutChildrenRenderPropsPipe,
@@ -144,7 +148,8 @@ import { LayoutChildrenRenderPropsPipe } from './layouts';
MasterListComponent,
JsonFormsDetailComponent,
ObjectControlRenderer,
- AutocompleteControlRenderer,
+ EnumControlRenderer,
+ OneOfEnumControlRenderer,
TableRenderer,
ArrayLayoutRenderer,
LayoutChildrenRenderPropsPipe,
diff --git a/packages/angular-material/src/library/other/master-detail/master.ts b/packages/angular-material/src/library/other/master-detail/master.ts
index 7e8ad9e49..ec865c9ef 100644
--- a/packages/angular-material/src/library/other/master-detail/master.ts
+++ b/packages/angular-material/src/library/other/master-detail/master.ts
@@ -101,7 +101,7 @@ export const removeSchemaKeywords = (path: string) => {
mat-icon-button
class="button item-button hide"
(click)="onDeleteClick(i)"
- [ngClass]="{ show: highlightedIdx == i }"
+ [ngClass]="{ show: highlightedIdx === i }"
*ngIf="isEnabled()"
>
delete
diff --git a/packages/angular-material/test/array-layout.spec.ts b/packages/angular-material/test/array-layout.spec.ts
index 47a94dd51..ac8aeef55 100644
--- a/packages/angular-material/test/array-layout.spec.ts
+++ b/packages/angular-material/test/array-layout.spec.ts
@@ -125,11 +125,11 @@ describe('Array layout', () => {
const arrayLayoutElement: HTMLElement = fixture.nativeElement;
const matBadgeElement =
- arrayLayoutElement.querySelector('.mat-badge-content')!;
+ arrayLayoutElement.querySelector('.mat-badge-content');
const noDataElement = arrayLayoutElement.children[0].children[1];
- expect(matBadgeElement.textContent).toBe('1');
+ expect(matBadgeElement?.textContent).toBe('1');
expect(noDataElement.textContent).toBe('No data');
});
});
@@ -149,9 +149,9 @@ describe('Array layout', () => {
const arrayLayoutElement: HTMLElement = fixture.nativeElement;
const matBadgeElement =
- arrayLayoutElement.querySelector('.mat-badge-content')!;
+ arrayLayoutElement.querySelector('.mat-badge-content');
- expect(matBadgeElement.textContent).toBe('2');
+ expect(matBadgeElement?.textContent).toBe('2');
});
});
@@ -170,9 +170,9 @@ describe('Array layout', () => {
const arrayLayoutElement: HTMLElement = fixture.nativeElement;
const matBadgeElement =
- arrayLayoutElement.querySelector('.mat-badge-content')!;
+ arrayLayoutElement.querySelector('.mat-badge-content');
- expect(matBadgeElement.textContent).toBe('4');
+ expect(matBadgeElement?.textContent).toBe('4');
});
});
});
diff --git a/packages/angular-material/test/autocomplete-control.spec.ts b/packages/angular-material/test/enum-control.spec.ts
similarity index 93%
rename from packages/angular-material/test/autocomplete-control.spec.ts
rename to packages/angular-material/test/enum-control.spec.ts
index fe685bf15..a99fc8837 100644
--- a/packages/angular-material/test/autocomplete-control.spec.ts
+++ b/packages/angular-material/test/enum-control.spec.ts
@@ -51,7 +51,7 @@ import {
JsonFormsCore,
EnumOption,
} from '@jsonforms/core';
-import { AutocompleteControlRenderer } from '../src';
+import { EnumControlRenderer } from '../src';
import { JsonFormsAngularService } from '@jsonforms/angular';
import { ErrorObject } from 'ajv';
import { HarnessLoader } from '@angular/cdk/testing';
@@ -81,16 +81,16 @@ const imports = [
ReactiveFormsModule,
];
const providers = [JsonFormsAngularService];
-const componentUT: any = AutocompleteControlRenderer;
+const componentUT: any = EnumControlRenderer;
const errorTest: ErrorTestExpectation = {
errorInstance: MatError,
numberOfElements: 1,
indexOfElement: 0,
};
-describe('Autocomplete control Base Tests', () => {
- let fixture: ComponentFixture;
- let component: AutocompleteControlRenderer;
+describe('Enum control Base Tests', () => {
+ let fixture: ComponentFixture;
+ let component: EnumControlRenderer;
let inputElement: HTMLInputElement;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
@@ -215,9 +215,9 @@ describe('Autocomplete control Base Tests', () => {
expect(inputElement.id).toBe('myId');
});
});
-describe('AutoComplete control Input Event Tests', () => {
- let fixture: ComponentFixture;
- let component: AutocompleteControlRenderer;
+describe('Enum control Input Event Tests', () => {
+ let fixture: ComponentFixture;
+ let component: EnumControlRenderer;
let loader: HarnessLoader;
let inputElement: HTMLInputElement;
beforeEach(waitForAsync(() => {
@@ -269,7 +269,7 @@ describe('AutoComplete control Input Event Tests', () => {
getJsonFormsService(component).updateCore(
Actions.init(data, schema, uischema)
);
- component.options = ['X', 'Y', 'Z'];
+ component.stringOptions = ['X', 'Y', 'Z'];
component.ngOnInit();
fixture.detectChanges();
@@ -339,9 +339,9 @@ describe('AutoComplete control Input Event Tests', () => {
expect(inputElement.value).toBe('Translated B');
}));
});
-describe('AutoComplete control Error Tests', () => {
- let fixture: ComponentFixture;
- let component: AutocompleteControlRenderer;
+describe('Enum control Error Tests', () => {
+ let fixture: ComponentFixture;
+ let component: EnumControlRenderer;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [componentUT, ...imports],
@@ -383,9 +383,9 @@ describe('AutoComplete control Error Tests', () => {
});
});
-describe('AutoComplete control updateFilter function', () => {
- let fixture: ComponentFixture;
- let component: AutocompleteControlRenderer;
+describe('Enum control updateFilter function', () => {
+ let fixture: ComponentFixture;
+ let component: EnumControlRenderer;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
@@ -401,7 +401,7 @@ describe('AutoComplete control updateFilter function', () => {
it('should not filter options on ENTER key press', () => {
component.shouldFilter = false;
- component.options = ['X', 'Y', 'Z'];
+ component.stringOptions = ['X', 'Y', 'Z'];
setupMockStore(fixture, { uischema, schema, data });
getJsonFormsService(component).updateCore(
Actions.init(data, schema, uischema)
@@ -415,7 +415,7 @@ describe('AutoComplete control updateFilter function', () => {
it('should filter options when a key other than ENTER is pressed', () => {
component.shouldFilter = false;
- component.options = ['X', 'Y', 'Z'];
+ component.stringOptions = ['X', 'Y', 'Z'];
setupMockStore(fixture, { uischema, schema, data });
getJsonFormsService(component).updateCore(
Actions.init(data, schema, uischema)
diff --git a/packages/angular-material/test/one-of-enum-control.spec.ts b/packages/angular-material/test/one-of-enum-control.spec.ts
new file mode 100644
index 000000000..1367265a0
--- /dev/null
+++ b/packages/angular-material/test/one-of-enum-control.spec.ts
@@ -0,0 +1,506 @@
+/*
+ The MIT License
+
+ Copyright (c) 2017-2019 EclipseSource Munich
+ https://github.com/eclipsesource/jsonforms
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+import {
+ MatAutocompleteModule,
+ MatAutocompleteSelectedEvent,
+} from '@angular/material/autocomplete';
+import { MatError, MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { DebugElement } from '@angular/core';
+import {
+ ComponentFixture,
+ fakeAsync,
+ TestBed,
+ tick,
+ waitForAsync,
+} from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { By } from '@angular/platform-browser';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import {
+ ErrorTestExpectation,
+ getJsonFormsService,
+ setupMockStore,
+} from './common';
+import {
+ Actions,
+ ControlElement,
+ EnumOption,
+ JsonFormsCore,
+ JsonSchema,
+} from '@jsonforms/core';
+import { OneOfEnumControlRenderer } 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 = {
+ oneOfEnum: 'foo',
+};
+const schema: JsonSchema = {
+ type: 'object',
+ properties: {
+ oneOfEnum: {
+ type: 'string',
+ oneOf: [
+ {
+ const: 'foo',
+ title: 'Foo',
+ },
+ {
+ const: 'bar',
+ title: 'Bar',
+ },
+ {
+ const: 'foobar',
+ title: 'FooBar',
+ },
+ ],
+ },
+ },
+};
+const uischema: ControlElement = {
+ type: 'Control',
+ scope: '#/properties/oneOfEnum',
+};
+
+const imports = [
+ MatAutocompleteModule,
+ MatInputModule,
+ MatFormFieldModule,
+ NoopAnimationsModule,
+ ReactiveFormsModule,
+];
+const providers = [JsonFormsAngularService];
+const componentUT: any = OneOfEnumControlRenderer;
+const errorTest: ErrorTestExpectation = {
+ errorInstance: MatError,
+ numberOfElements: 1,
+ indexOfElement: 0,
+};
+
+describe('OneOfEnum control Base Tests', () => {
+ let fixture: ComponentFixture;
+ let component: OneOfEnumControlRenderer;
+ let inputElement: HTMLInputElement;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [componentUT, ...imports],
+ providers: providers,
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(componentUT);
+ component = fixture.componentInstance;
+
+ inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
+ });
+
+ it('should render', fakeAsync(() => {
+ setupMockStore(fixture, { uischema, schema, data });
+ getJsonFormsService(component).updateCore(
+ Actions.init(data, schema, uischema)
+ );
+ component.ngOnInit();
+ fixture.detectChanges();
+ tick();
+ expect(component.data).toEqual('foo');
+ expect(inputElement.value).toBe('Foo');
+ expect(inputElement.disabled).toBe(false);
+ }));
+
+ it('should support updating the state', fakeAsync(() => {
+ setupMockStore(fixture, { uischema, schema, data });
+ getJsonFormsService(component).updateCore(
+ Actions.init(data, schema, uischema)
+ );
+ component.ngOnInit();
+ fixture.detectChanges();
+ tick();
+ getJsonFormsService(component).updateCore(
+ Actions.update('oneOfEnum', () => 'bar')
+ );
+ tick();
+ fixture.detectChanges();
+ expect(component.data).toEqual('bar');
+ expect(inputElement.value).toBe('Bar');
+ }));
+
+ it('should update with undefined value', () => {
+ setupMockStore(fixture, { uischema, schema, data });
+ getJsonFormsService(component).updateCore(
+ Actions.init(data, schema, uischema)
+ );
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ getJsonFormsService(component).updateCore(
+ Actions.update('oneOfEnum', () => undefined)
+ );
+ fixture.detectChanges();
+ expect(component.data).toBe(undefined);
+ expect(inputElement.value).toBe('');
+ });
+
+ it('should update with null value', () => {
+ setupMockStore(fixture, { uischema, schema, data });
+ getJsonFormsService(component).updateCore(
+ Actions.init(data, schema, uischema)
+ );
+ fixture.detectChanges();
+ component.ngOnInit();
+
+ getJsonFormsService(component).updateCore(
+ Actions.update('oneOfEnum', () => null)
+ );
+ fixture.detectChanges();
+ expect(component.data).toBe(null);
+ expect(inputElement.value).toBe('');
+ });
+
+ it('should not update with wrong ref', fakeAsync(() => {
+ setupMockStore(fixture, { uischema, schema, data });
+ getJsonFormsService(component).updateCore(
+ Actions.init(data, schema, uischema)
+ );
+ component.ngOnInit();
+ fixture.detectChanges();
+ tick();
+ getJsonFormsService(component).updateCore(
+ Actions.update('oneOfEnum', () => 'foo')
+ );
+ getJsonFormsService(component).updateCore(
+ Actions.update('plainEnum', () => 'bar')
+ );
+ fixture.detectChanges();
+ tick();
+ expect(component.data).toEqual('foo');
+ expect(inputElement.value).toBe('Foo');
+ }));
+
+ // store needed as we evaluate the calculated enabled value to disable/enable the control
+ it('can be disabled', () => {
+ setupMockStore(fixture, { uischema, schema, data });
+ getJsonFormsService(component).updateCore(
+ Actions.init(data, schema, uischema)
+ );
+ component.disabled = true;
+ component.ngOnInit();
+ fixture.detectChanges();
+ expect(inputElement.disabled).toBe(true);
+ });
+
+ it('can be hidden', () => {
+ setupMockStore(fixture, { uischema, schema, data });
+ getJsonFormsService(component).updateCore(
+ Actions.init(data, schema, uischema)
+ );
+ component.visible = false;
+ component.ngOnInit();
+ fixture.detectChanges();
+ const hasDisplayNone =
+ 'none' === fixture.nativeElement.children[0].style.display;
+ const hasHidden = fixture.nativeElement.children[0].hidden;
+ expect(hasDisplayNone || hasHidden).toBeTruthy();
+ });
+
+ it('id should be present in output', () => {
+ setupMockStore(fixture, { uischema, schema, data });
+ component.id = 'myId';
+ getJsonFormsService(component).updateCore(
+ Actions.init(data, schema, uischema)
+ );
+
+ fixture.detectChanges();
+ component.ngOnInit();
+ expect(inputElement.id).toBe('myId');
+ });
+});
+
+describe('OneOfEnum control Input Event Tests', () => {
+ let fixture: ComponentFixture;
+ let component: OneOfEnumControlRenderer;
+ let loader: HarnessLoader;
+ let inputElement: HTMLInputElement;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [componentUT, ...imports],
+ providers: [...providers],
+ }).compileComponents();
+ }));
+
+ beforeEach(waitForAsync(() => {
+ 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 () => {
+ setupMockStore(fixture, { uischema, schema, data });
+ 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: 'Bar' });
+ tick();
+ fixture.detectChanges();
+
+ expect(spy).toHaveBeenCalled();
+ const event = spy.calls.mostRecent()
+ .args[0] as MatAutocompleteSelectedEvent;
+
+ expect(event.option.value).toEqual({
+ label: 'Bar',
+ value: 'bar',
+ } satisfies EnumOption);
+ expect(inputElement.value).toBe('Bar');
+ }));
+
+ it('options should prefer own props', fakeAsync(async () => {
+ setupMockStore(fixture, { uischema, schema, data });
+ getJsonFormsService(component).updateCore(
+ Actions.init(data, schema, uischema)
+ );
+ component.options = [
+ {
+ label: 'X',
+ value: 'x',
+ },
+ {
+ label: 'Y',
+ value: 'y',
+ },
+ {
+ label: 'Z',
+ value: 'z',
+ },
+ ];
+
+ component.ngOnInit();
+ fixture.detectChanges();
+ const spy = spyOn(component, 'onSelect');
+
+ await (await loader.getHarness(MatAutocompleteHarness)).focus();
+ fixture.detectChanges();
+
+ await (
+ await loader.getHarness(MatAutocompleteHarness)
+ ).selectOption({ text: 'Y' });
+ fixture.detectChanges();
+ tick();
+
+ const event = spy.calls.mostRecent()
+ .args[0] as MatAutocompleteSelectedEvent;
+ 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 } = {
+ 'oneOfEnum.Foo': 'Translated Foo',
+ 'oneOfEnum.Bar': 'Translated Bar',
+ 'oneOfEnum.FooBar': 'Translated FooBar',
+ };
+ 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 Bar',
+ });
+ fixture.detectChanges();
+ tick();
+
+ const event = spy.calls.mostRecent()
+ .args[0] as MatAutocompleteSelectedEvent;
+ expect(event.option.value).toEqual({
+ label: 'Translated Bar',
+ value: 'bar',
+ } satisfies EnumOption);
+ expect(inputElement.value).toBe('Translated Bar');
+ }));
+});
+
+describe('OneOfEnum control Error Tests', () => {
+ let fixture: ComponentFixture;
+ let component: OneOfEnumControlRenderer;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [componentUT, ...imports],
+ providers: providers,
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(componentUT);
+ component = fixture.componentInstance;
+ });
+
+ it('should display errors', () => {
+ const errors: ErrorObject[] = [
+ {
+ instancePath: '/oneOfEnum',
+ message: 'Hi, this is me, test error!',
+ params: {},
+ keyword: '',
+ schemaPath: '',
+ },
+ ];
+ setupMockStore(fixture, {
+ uischema,
+ schema,
+ data,
+ });
+ const formsService = getJsonFormsService(component);
+ formsService.updateCore(Actions.updateErrors(errors));
+ formsService.refresh();
+
+ component.ngOnInit();
+ fixture.detectChanges();
+ const debugErrors: DebugElement[] = fixture.debugElement.queryAll(
+ By.directive(errorTest.errorInstance)
+ );
+ expect(debugErrors.length).toBe(errorTest.numberOfElements);
+ expect(
+ debugErrors[errorTest.indexOfElement].nativeElement.textContent
+ ).toBe('Hi, this is me, test error!');
+ });
+});
+
+describe('OneOfEnum control updateFilter function', () => {
+ let fixture: ComponentFixture;
+ let component: OneOfEnumControlRenderer;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [componentUT, ...imports],
+ providers: providers,
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(componentUT);
+ component = fixture.componentInstance;
+ });
+
+ it('should not filter options on ENTER key press', () => {
+ component.shouldFilter = false;
+ component.options = [
+ {
+ label: 'X',
+ value: 'x',
+ },
+ {
+ label: 'Y',
+ value: 'y',
+ },
+ {
+ label: 'Z',
+ value: 'z',
+ },
+ ];
+ setupMockStore(fixture, { uischema, schema, data });
+ getJsonFormsService(component).updateCore(
+ Actions.init(data, schema, uischema)
+ );
+ component.ngOnInit();
+ fixture.detectChanges();
+ component.updateFilter({ keyCode: 13 });
+ fixture.detectChanges();
+ expect(component.shouldFilter).toBe(false);
+ });
+
+ it('should filter options when a key other than ENTER is pressed', () => {
+ component.shouldFilter = false;
+ component.options = [
+ {
+ label: 'X',
+ value: 'x',
+ },
+ {
+ label: 'Y',
+ value: 'y',
+ },
+ {
+ label: 'Z',
+ value: 'z',
+ },
+ ];
+ setupMockStore(fixture, { uischema, schema, data });
+ getJsonFormsService(component).updateCore(
+ Actions.init(data, schema, uischema)
+ );
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ component.updateFilter({ keyCode: 65 });
+ fixture.detectChanges();
+
+ expect(component.shouldFilter).toBe(true);
+ });
+});