mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-46249 Normalize cascade dropdown parent value before child option lookup
This commit is contained in:
+143
@@ -1009,6 +1009,149 @@ describe('DropdownCloudWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('cascade parent value normalization', () => {
|
||||||
|
const parentDropdown = new FormFieldModel(new FormModel(), { id: 'parentDropdown', type: 'dropdown' });
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', readOnly: 'false' }), {
|
||||||
|
id: 'child-dropdown-id',
|
||||||
|
name: 'child-dropdown',
|
||||||
|
type: 'dropdown',
|
||||||
|
optionType: 'manual',
|
||||||
|
rule: {
|
||||||
|
ruleOn: 'parentDropdown',
|
||||||
|
entries: mockConditionalEntries
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve child options when parent value is an object { id, name }', () => {
|
||||||
|
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', readOnly: 'false' }), {
|
||||||
|
id: 'child-dropdown-id',
|
||||||
|
name: 'child-dropdown',
|
||||||
|
type: 'dropdown',
|
||||||
|
optionType: 'manual',
|
||||||
|
rule: { ruleOn: 'parentDropdown', entries: mockConditionalEntries }
|
||||||
|
});
|
||||||
|
const mockParentDropdown = { id: 'parentDropdown', value: { id: 'GR', name: 'Greece' }, validate: (): boolean => true };
|
||||||
|
spyOn(widget.field.form, 'getFormFields').and.returnValue([mockParentDropdown]);
|
||||||
|
widget.ngOnInit();
|
||||||
|
|
||||||
|
expect(widget.field.options).toEqual(mockConditionalEntries[0].options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve child options when parent value is a plain string (no regression)', () => {
|
||||||
|
parentDropdown.value = 'GR';
|
||||||
|
widget.selectionChangedForField(parentDropdown);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(widget.field.options).toEqual(mockConditionalEntries[0].options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear child options and not warn when parent value is null', () => {
|
||||||
|
const warnSpy = spyOn(console, 'warn');
|
||||||
|
parentDropdown.value = null;
|
||||||
|
widget.selectionChangedForField(parentDropdown);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(widget.field.options).toEqual([]);
|
||||||
|
expect(warnSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear child options and not warn when parent value is undefined', () => {
|
||||||
|
const warnSpy = spyOn(console, 'warn');
|
||||||
|
parentDropdown.value = undefined;
|
||||||
|
widget.selectionChangedForField(parentDropdown);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(widget.field.options).toEqual([]);
|
||||||
|
expect(warnSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve child options from first element when parent value is an array of objects', () => {
|
||||||
|
parentDropdown.value = [{ id: 'GR', name: 'Greece' }];
|
||||||
|
widget.selectionChangedForField(parentDropdown);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(widget.field.options).toEqual(mockConditionalEntries[0].options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve child options from first element when parent value is an array of strings', () => {
|
||||||
|
parentDropdown.value = ['GR', 'IT'];
|
||||||
|
widget.selectionChangedForField(parentDropdown);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(widget.field.options).toEqual(mockConditionalEntries[0].options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear child options and not warn when parent value is an empty string', () => {
|
||||||
|
const warnSpy = spyOn(console, 'warn');
|
||||||
|
widget.field.options = mockConditionalEntries[1].options;
|
||||||
|
parentDropdown.value = '';
|
||||||
|
widget.selectionChangedForField(parentDropdown);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(widget.field.options).toEqual([]);
|
||||||
|
expect(warnSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear child options and warn exactly once for unexpected value shapes', () => {
|
||||||
|
const warnSpy = spyOn(console, 'warn');
|
||||||
|
|
||||||
|
parentDropdown.value = 42;
|
||||||
|
widget.selectionChangedForField(parentDropdown);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
parentDropdown.value = { name: 'no id' };
|
||||||
|
widget.selectionChangedForField(parentDropdown);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(widget.field.options).toEqual([]);
|
||||||
|
expect(warnSpy.calls.count()).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for null input to normalizeParentValueToId', () => {
|
||||||
|
expect((widget as any).normalizeParentValueToId(null)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for undefined input to normalizeParentValueToId', () => {
|
||||||
|
expect((widget as any).normalizeParentValueToId(undefined)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for empty string input to normalizeParentValueToId', () => {
|
||||||
|
expect((widget as any).normalizeParentValueToId('')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the same string for string input to normalizeParentValueToId', () => {
|
||||||
|
expect((widget as any).normalizeParentValueToId('GR')).toBe('GR');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return id for { id, name } object input to normalizeParentValueToId', () => {
|
||||||
|
expect((widget as any).normalizeParentValueToId({ id: 'GR', name: 'Greece' })).toBe('GR');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return first element for array of strings input to normalizeParentValueToId', () => {
|
||||||
|
expect((widget as any).normalizeParentValueToId(['GR', 'IT'])).toBe('GR');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return id of first element for array of objects input to normalizeParentValueToId', () => {
|
||||||
|
expect((widget as any).normalizeParentValueToId([{ id: 'GR', name: 'Greece' }])).toBe('GR');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null and warn for unexpected shape input to normalizeParentValueToId', () => {
|
||||||
|
const warnSpy = spyOn(console, 'warn');
|
||||||
|
expect((widget as any).normalizeParentValueToId(42)).toBeNull();
|
||||||
|
expect(warnSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null and warn once for an empty array input to normalizeParentValueToId', () => {
|
||||||
|
const warnSpy = spyOn(console, 'warn');
|
||||||
|
expect((widget as any).normalizeParentValueToId([])).toBeNull();
|
||||||
|
expect(warnSpy.calls.count()).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('when form model has left labels', () => {
|
describe('when form model has left labels', () => {
|
||||||
it('should have left labels classes on leftLabels true', async () => {
|
it('should have left labels classes on leftLabels true', async () => {
|
||||||
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', readOnly: false, leftLabels: true }), {
|
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', readOnly: false, leftLabels: true }), {
|
||||||
|
|||||||
+42
-9
@@ -99,6 +99,8 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI
|
|||||||
private readonly defaultVariableOptionLabel = 'name';
|
private readonly defaultVariableOptionLabel = 'name';
|
||||||
private readonly defaultVariableOptionPath = 'data';
|
private readonly defaultVariableOptionPath = 'data';
|
||||||
|
|
||||||
|
private parentValueWarningLogged = false;
|
||||||
|
|
||||||
private readonly debounceSetValue = new Subject<void>();
|
private readonly debounceSetValue = new Subject<void>();
|
||||||
|
|
||||||
get showRequiredMessage(): boolean {
|
get showRequiredMessage(): boolean {
|
||||||
@@ -455,18 +457,49 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI
|
|||||||
takeUntilDestroyed(this.destroyRef)
|
takeUntilDestroyed(this.destroyRef)
|
||||||
)
|
)
|
||||||
.subscribe((event: FormFieldEvent) => {
|
.subscribe((event: FormFieldEvent) => {
|
||||||
const valueOfParentWidget = event.field.value;
|
const valueOfParentWidget = this.normalizeParentValueToId(event.field.value);
|
||||||
this.parentValueChanged(valueOfParentWidget);
|
this.parentValueChanged(valueOfParentWidget);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getParentWidgetValue(): string {
|
private normalizeParentValueToId(value: any): string | null {
|
||||||
const parentWidgetId = this.linkedWidgetId;
|
if (value === null || value === undefined || value === '') {
|
||||||
const parentWidget = this.getFormFieldById(parentWidgetId);
|
return null;
|
||||||
return parentWidget?.value;
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const first = value[0];
|
||||||
|
if (typeof first === 'string') {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
if (first && typeof first === 'object' && 'id' in first) {
|
||||||
|
return first.id;
|
||||||
|
}
|
||||||
|
return this.warnUnexpectedParentValue(value);
|
||||||
|
}
|
||||||
|
if (typeof value === 'object' && 'id' in value) {
|
||||||
|
return value.id;
|
||||||
|
}
|
||||||
|
return this.warnUnexpectedParentValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private parentValueChanged(value: string) {
|
private warnUnexpectedParentValue(value: any): null {
|
||||||
|
if (!this.parentValueWarningLogged) {
|
||||||
|
this.parentValueWarningLogged = true;
|
||||||
|
console.warn('DropdownCloudWidgetComponent: unexpected parent widget value shape for cascade lookup', value);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getParentWidgetValue(): string | null {
|
||||||
|
const parentWidgetId = this.linkedWidgetId;
|
||||||
|
const parentWidget = this.getFormFieldById(parentWidgetId);
|
||||||
|
return this.normalizeParentValueToId(parentWidget?.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private parentValueChanged(value: string | null) {
|
||||||
if (value && !this.isNoneValueSelected(value)) {
|
if (value && !this.isNoneValueSelected(value)) {
|
||||||
this.isValidRestConfig ? this.persistFieldOptionsFromRestApi() : this.persistFieldOptionsFromManualList(value);
|
this.isValidRestConfig ? this.persistFieldOptionsFromRestApi() : this.persistFieldOptionsFromManualList(value);
|
||||||
} else if (this.isNoneValueSelected(value)) {
|
} else if (this.isNoneValueSelected(value)) {
|
||||||
@@ -479,15 +512,15 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private isNoneValueSelected(value: string): boolean {
|
private isNoneValueSelected(value: string | null): boolean {
|
||||||
return value === undefined;
|
return value === undefined || value === null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFormFieldById(fieldId): FormFieldModel {
|
private getFormFieldById(fieldId): FormFieldModel {
|
||||||
return this.field.form.getFormFields().filter((field: FormFieldModel) => field.id === fieldId)[0];
|
return this.field.form.getFormFields().filter((field: FormFieldModel) => field.id === fieldId)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
private persistFieldOptionsFromManualList(value: string) {
|
private persistFieldOptionsFromManualList(value: string | null) {
|
||||||
if (this.hasRuleEntries()) {
|
if (this.hasRuleEntries()) {
|
||||||
const rulesEntries = this.field.rule.entries;
|
const rulesEntries = this.field.rule.entries;
|
||||||
rulesEntries.forEach((ruleEntry: RuleEntry) => {
|
rulesEntries.forEach((ruleEntry: RuleEntry) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user