0">
diff --git a/frontend/app/features/content/shared/forms/array-editor.component.ts b/frontend/app/features/content/shared/forms/array-editor.component.ts
index b5d1befa7..eac56c6df 100644
--- a/frontend/app/features/content/shared/forms/array-editor.component.ts
+++ b/frontend/app/features/content/shared/forms/array-editor.component.ts
@@ -77,6 +77,10 @@ export class ArrayEditorComponent implements OnChanges {
this.formModel.addItem(value);
}
+ public clear() {
+ this.formModel.reset();
+ }
+
public sort(event: CdkDragDrop
>) {
this.formModel.sort(sorted(event));
diff --git a/frontend/app/features/content/shared/forms/field-editor.component.ts b/frontend/app/features/content/shared/forms/field-editor.component.ts
index 5c750e855..409ddca95 100644
--- a/frontend/app/features/content/shared/forms/field-editor.component.ts
+++ b/frontend/app/features/content/shared/forms/field-editor.component.ts
@@ -88,6 +88,6 @@ export class FieldEditorComponent implements OnChanges {
}
public unset() {
- this.formModel.form.setValue(undefined);
+ this.formModel.unset();
}
}
\ No newline at end of file
diff --git a/frontend/app/framework/angular/forms/undefinable-form-array.spec.ts b/frontend/app/framework/angular/forms/undefinable-form-array.spec.ts
new file mode 100644
index 000000000..99007962f
--- /dev/null
+++ b/frontend/app/framework/angular/forms/undefinable-form-array.spec.ts
@@ -0,0 +1,89 @@
+/*
+ * Squidex Headless CMS
+ *
+ * @license
+ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
+ */
+
+import { FormArray, FormControl } from '@angular/forms';
+import { UndefinableFormArray } from './undefinable-form-array';
+
+describe('UndefinableFormArray', () => {
+ const tests = [{
+ name: 'undefined',
+ value: undefined
+ }, {
+ name: 'defined',
+ value: ['1']
+ }];
+
+ tests.forEach(x => {
+ it(`should set value as ${x.name}`, () => {
+ const control =
+ new UndefinableFormArray([
+ new FormControl('')
+ ]);
+
+ assertValue(control, x.value, () => {
+ control.setValue(x.value);
+ });
+ });
+ });
+
+ tests.forEach(x => {
+ it(`should patch value as ${x.name}`, () => {
+ const control =
+ new UndefinableFormArray([
+ new FormControl('')
+ ]);
+
+ assertValue(control, x.value, () => {
+ control.patchValue(x.value);
+ });
+ });
+ });
+
+ tests.forEach(x => {
+ it(`should reset value as ${x.name}`, () => {
+ const control =
+ new UndefinableFormArray([
+ new FormControl('')
+ ]);
+
+ assertValue(control, x.value, () => {
+ control.reset(x.value);
+ });
+ });
+ });
+
+ it('should reset value back after push', () => {
+ const control = new UndefinableFormArray([]);
+
+ assertValue(control, ['1'], () => {
+ control.setValue(undefined);
+ control.push(new FormControl('1'));
+ });
+ });
+
+ it('should reset value back after insert', () => {
+ const control = new UndefinableFormArray([]);
+
+ assertValue(control, ['1'], () => {
+ control.setValue(undefined);
+ control.insert(0, new FormControl('1'));
+ });
+ });
+
+ function assertValue(control: FormArray, expected: any, action: () => void) {
+ let currentValue: any;
+
+ control.valueChanges.subscribe(value => {
+ currentValue = value;
+ });
+
+ action();
+
+ expect(currentValue).toEqual(expected);
+ expect(control.getRawValue()).toEqual(expected);
+ }
+});
\ No newline at end of file
diff --git a/frontend/app/framework/angular/forms/undefinable-form-array.ts b/frontend/app/framework/angular/forms/undefinable-form-array.ts
new file mode 100644
index 000000000..0cb113842
--- /dev/null
+++ b/frontend/app/framework/angular/forms/undefinable-form-array.ts
@@ -0,0 +1,97 @@
+/*
+ * Squidex Headless CMS
+ *
+ * @license
+ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
+ */
+
+// tslint:disable: readonly-array
+
+import { EventEmitter } from '@angular/core';
+import { AbstractControl, AbstractControlOptions, AsyncValidatorFn, FormArray, ValidatorFn } from '@angular/forms';
+import { Types } from '@app/framework/internal';
+
+export class UndefinableFormArray extends FormArray {
+ private isUndefined = false;
+
+ constructor(controls: AbstractControl[], validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null) {
+ super(controls, validatorOrOpts, asyncValidator);
+
+ const reduce = this['_reduceValue'];
+
+ this['_reduceValue'] = () => {
+ if (this.isUndefined) {
+ return undefined;
+ } else {
+ return reduce();
+ }
+ };
+ }
+
+ public getRawValue() {
+ if (this.isUndefined) {
+ return undefined as any;
+ } else {
+ return super.getRawValue();
+ }
+ }
+
+ public push(control: AbstractControl) {
+ this.isUndefined = false;
+
+ super.push(control);
+ }
+
+ public insert(index: number, control: AbstractControl) {
+ this.isUndefined = false;
+
+ super.insert(index, control);
+ }
+
+ public setValue(value: any[] | undefined, options?: { onlySelf?: boolean; emitEvent?: boolean; }) {
+ this.isUndefined = Types.isUndefined(value);
+
+ if (this.isUndefined) {
+ super.reset([], options);
+ } else {
+ super.setValue(value!, options);
+ }
+ }
+
+ public patchValue(value: any[] | undefined, options?: { onlySelf?: boolean; emitEvent?: boolean; }) {
+ this.isUndefined = Types.isUndefined(value);
+
+ if (this.isUndefined) {
+ super.reset([], options);
+ } else {
+ super.patchValue(value!, options);
+ }
+ }
+
+ public reset(value: any[] | undefined, options?: { onlySelf?: boolean; emitEvent?: boolean; }) {
+ this.isUndefined = Types.isUndefined(value);
+
+ super.reset(value || [], options);
+ }
+
+ public updateValueAndValidity(opts: { onlySelf?: boolean; emitEvent?: boolean; } = {}) {
+ super.updateValueAndValidity({ emitEvent: false, onlySelf: true });
+
+ if (this.isUndefined) {
+ this.unsetValue();
+ }
+
+ if (opts.emitEvent !== false) {
+ (this.valueChanges as EventEmitter).emit(this.value);
+ (this.statusChanges as EventEmitter).emit(this.status);
+ }
+
+ if (this.parent && !opts.onlySelf) {
+ this.parent.updateValueAndValidity(opts);
+ }
+ }
+
+ private unsetValue() {
+ (this as { value: any }).value = undefined;
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/framework/declarations.ts b/frontend/app/framework/declarations.ts
index 47c823986..758990950 100644
--- a/frontend/app/framework/declarations.ts
+++ b/frontend/app/framework/declarations.ts
@@ -33,6 +33,7 @@ export * from './angular/forms/indeterminate-value.directive';
export * from './angular/forms/model';
export * from './angular/forms/progress-bar.component';
export * from './angular/forms/transform-input.directive';
+export * from './angular/forms/undefinable-form-array';
export * from './angular/forms/validators';
export * from './angular/highlight.pipe';
export * from './angular/hover-background.directive';
diff --git a/frontend/app/shared/services/schemas.spec.ts b/frontend/app/shared/services/schemas.spec.ts
new file mode 100644
index 000000000..be6e781f3
--- /dev/null
+++ b/frontend/app/shared/services/schemas.spec.ts
@@ -0,0 +1,119 @@
+ /*
+ * Squidex Headless CMS
+ *
+ * @license
+ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
+ */
+
+// tslint:disable: max-line-length
+
+import { createProperties, MetaFields, SchemaPropertiesDto } from '@app/shared/internal';
+import { TestValues } from './../state/_test-helpers';
+
+const {
+ createField,
+ createSchema} = TestValues;
+
+describe('SchemaDetailsDto', () => {
+ const field1 = createField({ properties: createProperties('Array'), id: 1 });
+ const field2 = createField({ properties: createProperties('Array'), id: 2 });
+ const field3 = createField({ properties: createProperties('Array'), id: 3 });
+
+ it('should return label as display name', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto('Label') });
+
+ expect(schema.displayName).toBe('Label');
+ });
+
+ it('should return name as display name if label is undefined', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto(undefined) });
+
+ expect(schema.displayName).toBe('schema1');
+ });
+
+ it('should return name as display name label is empty', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto('') });
+
+ expect(schema.displayName).toBe('schema1');
+ });
+
+ it('should return configured fields as list fields if fields are declared', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3], fieldsInLists: ['field1', 'field3'] });
+
+ expect(schema.defaultListFields).toEqual([field1, field3]);
+ });
+
+ it('should return first fields as list fields if no field is declared', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3] });
+
+ expect(schema.defaultListFields).toEqual([MetaFields.lastModifiedByAvatar, field1, MetaFields.statusColor, MetaFields.lastModified]);
+ });
+
+ it('should return preset with empty content field as list fields if fields is empty', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto() });
+
+ expect(schema.defaultListFields).toEqual([MetaFields.lastModifiedByAvatar, '', MetaFields.statusColor, MetaFields.lastModified]);
+ });
+
+ it('should return configured fields as references fields if fields are declared', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3], fieldsInReferences: ['field1', 'field3'] });
+
+ expect(schema.defaultReferenceFields).toEqual([field1, field3]);
+ });
+
+ it('should return first field as reference fields if no field is declared', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3] });
+
+ expect(schema.defaultReferenceFields).toEqual([field1]);
+ });
+
+ it('should return noop field as reference field if list is empty', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto() });
+
+ expect(schema.defaultReferenceFields).toEqual(['']);
+ });
+});
+
+describe('FieldDto', () => {
+ it('should return label as display name', () => {
+ const field = createField({ properties: createProperties('Array', { label: 'Label' }) });
+
+ expect(field.displayName).toBe('Label');
+ });
+
+ it('should return name as display name if label is null', () => {
+ const field = createField({ properties: createProperties('Assets') });
+
+ expect(field.displayName).toBe('field1');
+ });
+
+ it('should return name as display name label is empty', () => {
+ const field = createField({ properties: createProperties('Assets', { label: '' }) });
+
+ expect(field.displayName).toBe('field1');
+ });
+
+ it('should return placeholder as display placeholder', () => {
+ const field = createField({ properties: createProperties('Assets', { placeholder: 'Placeholder' }) });
+
+ expect(field.displayPlaceholder).toBe('Placeholder');
+ });
+
+ it('should return empty as display placeholder if placeholder is null', () => {
+ const field = createField({ properties: createProperties('Assets') });
+
+ expect(field.displayPlaceholder).toBe('');
+ });
+
+ it('should return localizable if partitioning is language', () => {
+ const field = createField({ properties: createProperties('Assets'), partitioning: 'language' });
+
+ expect(field.isLocalizable).toBeTruthy();
+ });
+
+ it('should not return localizable if partitioning is invariant', () => {
+ const field = createField({ properties: createProperties('Assets'), partitioning: 'invariant' });
+
+ expect(field.isLocalizable).toBeFalsy();
+ });
+});
\ No newline at end of file
diff --git a/frontend/app/shared/state/_test-helpers.ts b/frontend/app/shared/state/_test-helpers.ts
index b8f86f770..135bf28cc 100644
--- a/frontend/app/shared/state/_test-helpers.ts
+++ b/frontend/app/shared/state/_test-helpers.ts
@@ -7,7 +7,7 @@
import { of } from 'rxjs';
import { Mock } from 'typemoq';
-import { AppsState, AuthService, DateTime, Version } from './../';
+import { AppsState, AuthService, DateTime, FieldPropertiesDto, FieldRule, NestedFieldDto, RootFieldDto, SchemaDetailsDto, SchemaPropertiesDto, Version } from './../';
const app = 'my-app';
const creation = DateTime.today().addDays(-2);
@@ -30,10 +30,62 @@ const authService = Mock.ofType();
authService.setup(x => x.user)
.returns(() => { id: modifier, token: modifier });
+type SchemaValues = {
+ id?: number;
+ fields?: ReadonlyArray;
+ fieldsInLists?: ReadonlyArray;
+ fieldsInReferences?: ReadonlyArray;
+ fieldRules?: ReadonlyArray;
+ properties?: SchemaPropertiesDto;
+};
+
+function createSchema({ properties, id, fields, fieldsInLists, fieldsInReferences, fieldRules }: SchemaValues = {}) {
+ id = id || 1;
+
+ return new SchemaDetailsDto({},
+ `schema${1}`,
+ `schema${1}`,
+ 'category',
+ properties || new SchemaPropertiesDto(), false, true,
+ creation,
+ creator,
+ modified,
+ modifier,
+ new Version('1'),
+ fields,
+ fieldsInLists || [],
+ fieldsInReferences || [],
+ fieldRules || []);
+}
+
+type FieldValues = {
+ id?: number;
+ properties: FieldPropertiesDto;
+ isDisabled?: boolean,
+ isHidden?: boolean,
+ partitioning?: string;
+ nested?: ReadonlyArray
+};
+
+function createField({ properties, id, partitioning, isDisabled, nested }: FieldValues) {
+ id = id || 1;
+
+ return new RootFieldDto({}, id, `field${id}`, properties, partitioning || 'language', false, false, isDisabled, nested);
+}
+
+function createNestedField({ properties, id, isDisabled }: FieldValues) {
+ id = id || 1;
+
+ return new NestedFieldDto({}, id, `nested${id}`, properties, 0, false, false, isDisabled);
+}
+
export const TestValues = {
app,
appsState,
authService,
+ createField,
+ createNestedField,
+ createSchema,
creation,
creator,
modified,
diff --git a/frontend/app/shared/state/contents.forms-helpers.ts b/frontend/app/shared/state/contents.forms-helpers.ts
index ed7c89535..809844185 100644
--- a/frontend/app/shared/state/contents.forms-helpers.ts
+++ b/frontend/app/shared/state/contents.forms-helpers.ts
@@ -164,6 +164,10 @@ export abstract class AbstractContentForm {
- const field1 = createField({ properties: createProperties('Array'), id: 1 });
- const field2 = createField({ properties: createProperties('Array'), id: 2 });
- const field3 = createField({ properties: createProperties('Array'), id: 3 });
-
- it('should return label as display name', () => {
- const schema = createSchema({ properties: new SchemaPropertiesDto('Label') });
-
- expect(schema.displayName).toBe('Label');
- });
-
- it('should return name as display name if label is undefined', () => {
- const schema = createSchema({ properties: new SchemaPropertiesDto(undefined) });
-
- expect(schema.displayName).toBe('schema1');
- });
-
- it('should return name as display name label is empty', () => {
- const schema = createSchema({ properties: new SchemaPropertiesDto('') });
-
- expect(schema.displayName).toBe('schema1');
- });
-
- it('should return configured fields as list fields if fields are declared', () => {
- const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3], fieldsInLists: ['field1', 'field3'] });
-
- expect(schema.defaultListFields).toEqual([field1, field3]);
- });
-
- it('should return first fields as list fields if no field is declared', () => {
- const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3] });
-
- expect(schema.defaultListFields).toEqual([MetaFields.lastModifiedByAvatar, field1, MetaFields.statusColor, MetaFields.lastModified]);
- });
-
- it('should return preset with empty content field as list fields if fields is empty', () => {
- const schema = createSchema({ properties: new SchemaPropertiesDto() });
-
- expect(schema.defaultListFields).toEqual([MetaFields.lastModifiedByAvatar, '', MetaFields.statusColor, MetaFields.lastModified]);
- });
-
- it('should return configured fields as references fields if fields are declared', () => {
- const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3], fieldsInReferences: ['field1', 'field3'] });
-
- expect(schema.defaultReferenceFields).toEqual([field1, field3]);
- });
-
- it('should return first field as reference fields if no field is declared', () => {
- const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3] });
-
- expect(schema.defaultReferenceFields).toEqual([field1]);
- });
-
- it('should return noop field as reference field if list is empty', () => {
- const schema = createSchema({ properties: new SchemaPropertiesDto() });
-
- expect(schema.defaultReferenceFields).toEqual(['']);
- });
-});
-
-describe('FieldDto', () => {
- it('should return label as display name', () => {
- const field = createField({ properties: createProperties('Array', { label: 'Label' }) });
-
- expect(field.displayName).toBe('Label');
- });
-
- it('should return name as display name if label is null', () => {
- const field = createField({ properties: createProperties('Assets') });
-
- expect(field.displayName).toBe('field1');
- });
-
- it('should return name as display name label is empty', () => {
- const field = createField({ properties: createProperties('Assets', { label: '' }) });
-
- expect(field.displayName).toBe('field1');
- });
-
- it('should return placeholder as display placeholder', () => {
- const field = createField({ properties: createProperties('Assets', { placeholder: 'Placeholder' }) });
-
- expect(field.displayPlaceholder).toBe('Placeholder');
- });
-
- it('should return empty as display placeholder if placeholder is null', () => {
- const field = createField({ properties: createProperties('Assets') });
-
- expect(field.displayPlaceholder).toBe('');
- });
-
- it('should return localizable if partitioning is language', () => {
- const field = createField({ properties: createProperties('Assets'), partitioning: 'language' });
-
- expect(field.isLocalizable).toBeTruthy();
- });
-
- it('should not return localizable if partitioning is invarient', () => {
- const field = createField({ properties: createProperties('Assets'), partitioning: 'invariant' });
-
- expect(field.isLocalizable).toBeFalsy();
- });
-});
-
-describe('ArrayField', () => {
- const field = createField({ properties: createProperties('Array', { isRequired: true, minItems: 1, maxItems: 5 }) });
-
- it('should create validators', () => {
- expect(FieldsValidators.create(field, false).length).toBe(2);
- });
-
- it('should format to empty string if null', () => {
- expect(FieldFormatter.format(field, null)).toBe('');
- });
-
- it('should format to plural count for many items', () => {
- expect(FieldFormatter.format(field, [1, 2, 3])).toBe('3 Items');
- });
-
- it('should format to plural count for single item', () => {
- expect(FieldFormatter.format(field, [1])).toBe('1 Item');
- });
-
- it('should return zero formatting if other type', () => {
- expect(FieldFormatter.format(field, 1)).toBe('0 Items');
- });
-
- it('should return default value as null', () => {
- expect(FieldDefaultValue.get(field, 'iv')).toBeNull();
- });
-});
-
-describe('AssetsField', () => {
- const field = createField({ properties: createProperties('Assets', { isRequired: true, minItems: 1, maxItems: 5 }) });
-
- it('should create validators', () => {
- expect(FieldsValidators.create(field, false).length).toBe(3);
- });
-
- it('should format to empty string if null', () => {
- expect(FieldFormatter.format(field, null)).toBe('');
- });
-
- it('should format to plural count for many items', () => {
- expect(FieldFormatter.format(field, [1, 2, 3])).toBe('3 Assets');
- });
-
- it('should format to plural count for single item', () => {
- expect(FieldFormatter.format(field, [1])).toBe('1 Asset');
- });
-
- it('should return zero formatting if other type', () => {
- expect(FieldFormatter.format(field, 1)).toBe('0 Assets');
- });
-
- it('should return default value from properties', () => {
- const field2 = createField({ properties: createProperties('Assets', { defaultValue: ['1', '2'] }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toEqual(['1', '2']);
- });
-
- it('should override default value from localizable properties', () => {
- const field2 = createField({ properties: createProperties('Assets', { defaultValue: ['1', '2'], defaultValues: { 'iv': null } }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
- });
-});
-
-describe('TagsField', () => {
- const field = createField({ properties: createProperties('Tags', { isRequired: true, minItems: 1, maxItems: 5 }) });
-
- it('should create validators', () => {
- expect(FieldsValidators.create(field, false).length).toBe(2);
- });
-
- it('should format to empty string if null', () => {
- expect(FieldFormatter.format(field, null)).toBe('');
- });
-
- it('should format to asset count', () => {
- expect(FieldFormatter.format(field, ['hello', 'squidex', 'cms'])).toBe('hello, squidex, cms');
- });
-
- it('should return zero formatting if other type', () => {
- expect(FieldFormatter.format(field, 1)).toBe('');
- });
-
- it('should return default value from properties', () => {
- const field2 = createField({ properties: createProperties('Tags', { defaultValue: ['1', '2'] }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toEqual(['1', '2']);
- });
-
- it('should override default value from localizable properties', () => {
- const field2 = createField({ properties: createProperties('Tags', { defaultValue: ['1', '2'], defaultValues: { 'iv': null } }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
- });
-});
-
-describe('BooleanField', () => {
- const field = createField({ properties: createProperties('Boolean', { editor: 'Checkbox', isRequired: true }) });
-
- it('should create validators', () => {
- expect(FieldsValidators.create(field, false).length).toBe(1);
- });
-
- it('should format to empty string if null', () => {
- expect(FieldFormatter.format(field, null)).toBe('');
- });
-
- it('should format to Yes if true', () => {
- expect(FieldFormatter.format(field, true)).toBe('Yes');
- });
-
- it('should format to No if false', () => {
- expect(FieldFormatter.format(field, false)).toBe('No');
- });
-
- it('should return default value from properties', () => {
- const field2 = createField({ properties: createProperties('Boolean', { editor: 'Checkbox', defaultValue: true }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toBeTruthy();
- });
-
- it('should override default value from localizable properties', () => {
- const field2 = createField({ properties: createProperties('Boolean', { defaultValue: true, defaultValues: { 'iv': null } }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
- });
-});
-
-describe('DateTimeField', () => {
- const field = createField({ properties: createProperties('DateTime', { editor: 'DateTime', isRequired: true }) });
-
- beforeEach(() => {
- DateHelper.setlocale(null);
- });
-
- it('should create validators', () => {
- expect(FieldsValidators.create(field, false).length).toBe(1);
- });
-
- it('should format to empty string if null', () => {
- expect(FieldFormatter.format(field, null)).toBe('');
- });
-
- it('should format to input if parsing failed', () => {
- expect(FieldFormatter.format(field, true)).toBe(true);
- });
-
- it('should format old format to date', () => {
- const dateField = createField({ properties: createProperties('DateTime', { editor: 'Date' }) });
-
- expect(FieldFormatter.format(dateField, '2017-12-12')).toBe('12/12/2017');
- });
-
- it('should format datetime to date', () => {
- const dateField = createField({ properties: createProperties('DateTime', { editor: 'Date' }) });
-
- expect(FieldFormatter.format(dateField, '2017-12-12T16:00:00Z')).toBe('12/12/2017');
- });
-
- it('should format date to date', () => {
- const dateField = createField({ properties: createProperties('DateTime', { editor: 'Date' }) });
-
- expect(FieldFormatter.format(dateField, '2017-12-12T00:00:00Z')).toBe('12/12/2017');
- });
-
- it('should format to date time', () => {
- const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime' }) });
-
- expect(FieldFormatter.format(field2, '2017-12-12T16:00:00Z')).toBe('12/12/2017, 4:00:00 PM');
- });
-
- it('should return default from properties value', () => {
- const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime', defaultValue: '2017-10-12T16:00:00Z' }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toEqual('2017-10-12T16:00:00Z');
- });
-
- it('should override default value from localizable properties', () => {
- const field2 = createField({ properties: createProperties('DateTime', { defaultValue: '2017-10-12T16:00:00Z', defaultValues: { 'iv': null } }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
- });
-
- it('should return default from Today', () => {
- const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime', calculatedDefaultValue: 'Today' }) });
-
- expect(FieldDefaultValue.get(field2, 'iv', now)).toEqual('2017-10-12T00:00:00Z');
- });
-
- it('should return default value from Today', () => {
- const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime', calculatedDefaultValue: 'Now' }) });
-
- expect(FieldDefaultValue.get(field2, 'iv', now)).toEqual('2017-10-12T16:30:10Z');
- });
-});
-
-describe('GeolocationField', () => {
- const field = createField({ properties: createProperties('Geolocation', { isRequired: true }) });
-
- it('should create validators', () => {
- expect(FieldsValidators.create(field, false).length).toBe(1);
- });
-
- it('should format to empty string if null', () => {
- expect(FieldFormatter.format(field, null)).toBe('');
- });
-
- it('should format to latitude and longitude', () => {
- expect(FieldFormatter.format(field, { latitude: 42, longitude: 3.14 })).toBe('3.14, 42');
- });
-
- it('should return default value as null', () => {
- expect(FieldDefaultValue.get(field, 'iv')).toBeNull();
- });
-});
-
-describe('JsonField', () => {
- const field = createField({ properties: createProperties('Json', { isRequired: true }) });
-
- it('should create validators', () => {
- expect(FieldsValidators.create(field, false).length).toBe(1);
- });
-
- it('should format to empty string if null', () => {
- expect(FieldFormatter.format(field, null)).toBe('');
- });
-
- it('should format to constant', () => {
- expect(FieldFormatter.format(field, {})).toBe('');
- });
-
- it('should return default value as null', () => {
- expect(FieldDefaultValue.get(field, 'iv')).toBeNull();
- });
-});
-
-describe('NumberField', () => {
- const field = createField({ properties: createProperties('Number', { isRequired: true, minValue: 1, maxValue: 6, allowedValues: [1, 3] }) });
-
- it('should create validators', () => {
- expect(FieldsValidators.create(field, false).length).toBe(3);
- });
-
- it('should format to empty string if null', () => {
- expect(FieldFormatter.format(field, null)).toBe('');
- });
-
- it('should format to number', () => {
- expect(FieldFormatter.format(field, 42)).toEqual('42');
- });
-
- it('should format to stars if html allowed', () => {
- const field2 = createField({ properties: createProperties('Number', { editor: 'Stars' }) });
-
- expect(FieldFormatter.format(field2, 3)).toEqual(new HtmlValue('★ ★ ★ '));
- });
-
- it('should format to short star view for many stars', () => {
- const field2 = createField({ properties: createProperties('Number', { editor: 'Stars' }) });
-
- expect(FieldFormatter.format(field2, 42)).toEqual(new HtmlValue('★ 42'));
- });
-
- it('should format to short star view for no stars', () => {
- const field2 = createField({ properties: createProperties('Number', { editor: 'Stars' }) });
-
- expect(FieldFormatter.format(field2, 0)).toEqual(new HtmlValue('★ 0'));
- });
-
- it('should format to short star view for negative stars', () => {
- const field2 = createField({ properties: createProperties('Number', { editor: 'Stars' }) });
-
- expect(FieldFormatter.format(field2, -13)).toEqual(new HtmlValue('★ -13'));
- });
-
- it('should not format to stars if html not allowed', () => {
- const field2 = createField({ properties: createProperties('Number', { editor: 'Stars' }) });
-
- expect(FieldFormatter.format(field2, 3, false)).toEqual('3');
- });
-
- it('should return default value from properties', () => {
- const field2 = createField({ properties: createProperties('Number', { defaultValue: 13 }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toEqual(13);
- });
-
- it('should override default value from localizable properties', () => {
- const field2 = createField({ properties: createProperties('Number', { defaultValue: 13, defaultValues: { 'iv': null } }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
- });
-});
-
-describe('ReferencesField', () => {
- const field = createField({ properties: createProperties('References', { editor: 'List', isRequired: true, minItems: 1, maxItems: 5 }) });
-
- it('should create validators', () => {
- expect(FieldsValidators.create(field, false).length).toBe(3);
- });
-
- it('should format to empty string if null', () => {
- expect(FieldFormatter.format(field, null)).toBe('');
- });
-
- it('should format to plural count for many items', () => {
- expect(FieldFormatter.format(field, [1, 2, 3])).toBe('3 References');
- });
-
- it('should format to plural count for single item', () => {
- expect(FieldFormatter.format(field, [1])).toBe('1 Reference');
- });
-
- it('should return zero formatting if other type', () => {
- expect(FieldFormatter.format(field, 1)).toBe('0 References');
- });
-
- it('should return default value from properties', () => {
- const field2 = createField({ properties: createProperties('References', { defaultValue: ['1', '2'] }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toEqual(['1', '2']);
- });
-
- it('should override default value from localizable properties', () => {
- const field2 = createField({ properties: createProperties('References', { defaultValue: ['1', '2'], defaultValues: { 'iv': null } }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
- });
-});
-
-describe('StringField', () => {
- const field = createField({ properties: createProperties('String', { isRequired: true, pattern: 'pattern', minLength: 1, maxLength: 5, allowedValues: ['a', 'b'] }) });
-
- it('should create validators', () => {
- expect(FieldsValidators.create(field, false).length).toBe(4);
- });
-
- it('should format to empty string if null', () => {
- expect(FieldFormatter.format(field, null)).toBe('');
- });
-
- it('should format to string', () => {
- expect(FieldFormatter.format(field, 'hello')).toBe('hello');
- });
-
- it('should format to preview image', () => {
- const field2 = createField({ properties: createProperties('String', { editor: 'StockPhoto' }) });
-
- expect(FieldFormatter.format(field2, 'https://images.unsplash.com/123?x', true)).toEqual(new HtmlValue('
'));
- });
-
- it('should not format to preview image when html not allowed', () => {
- const field2 = createField({ properties: createProperties('String', { editor: 'StockPhoto' }) });
-
- expect(FieldFormatter.format(field2, 'https://images.unsplash.com/123?x', false)).toBe('https://images.unsplash.com/123?x');
- });
-
- it('should not format to preview image when not unsplash image', () => {
- const field2 = createField({ properties: createProperties('String', { editor: 'StockPhoto' }) });
-
- expect(FieldFormatter.format(field2, 'https://images.com/123?x', true)).toBe('https://images.com/123?x');
- });
-
- it('should return default value from properties', () => {
- const field2 = createField({ properties: createProperties('String', { defaultValue: 'MyDefault' }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toEqual('MyDefault');
- });
-
- it('should override default value from localizable properties', () => {
- const field2 = createField({ properties: createProperties('String', { defaultValue: 'MyDefault', defaultValues: { 'iv': null } }) });
-
- expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
- });
-});
-
describe('GetContentValue', () => {
const language = new LanguageDto('en', 'English');
const fieldInvariant = createField({ properties: createProperties('Number'), partitioning: 'invariant' });
@@ -910,12 +426,7 @@ describe('ContentForm', () => {
});
it('should load with array and not enable disabled nested fields', () => {
- const contentForm = createForm([
- createField({ id: 4, properties: createProperties('Array'), partitioning: 'invariant', nested: [
- createNestedField({ id: 41, properties: createProperties('String') }),
- createNestedField({ id: 42, properties: createProperties('String'), isDisabled: true })
- ]})
- ]);
+ const { contentForm, array } = createArrayFormWith2Items();
contentForm.load({
field4: {
@@ -925,64 +436,99 @@ describe('ContentForm', () => {
}
});
- const nestedForm = contentForm.form.get('field4.iv') as FormArray;
- const nestedItem = nestedForm.get([0])!;
-
- expect(nestedForm.controls.length).toBe(1);
+ const nestedItem = array.form.get([0])!;
+ expectLength(array, 1);
expectForm(nestedItem, 'nested41', { disabled: false, value: 'Text' });
expectForm(nestedItem, 'nested42', { disabled: true, value: null });
});
it('should add array item', () => {
- const contentForm = createForm([
- createField({ id: 4, properties: createProperties('Array'), partitioning: 'invariant', nested: [
- createNestedField({ id: 41, properties: createProperties('String') }),
- createNestedField({ id: 42, properties: createProperties('String', { defaultValue: 'Default' }), isDisabled: true })
- ]})
- ]);
-
- const array = contentForm.get(complexSchema.fields[3])!.get(languages[0]) as FieldArrayForm;
+ const { array } = createArrayFormWith2Items();
array.addItem();
- array.addItem();
-
- const nestedForm = contentForm.form.get('field4.iv') as FormArray;
- const nestedItem = nestedForm.get([0])!;
- expect(nestedForm.controls.length).toBe(2);
+ const nestedItem = array.form.get([2])!;
+ expectLength(array, 3);
expectForm(nestedItem, 'nested41', { disabled: false, value: null });
expectForm(nestedItem, 'nested42', { disabled: true, value: 'Default' });
});
- it('should remove array item', () => {
- const contentForm = createForm([
- createField({ id: 4, properties: createProperties('Array'), partitioning: 'invariant', nested: [
- createNestedField({ id: 41, properties: createProperties('String') })
- ]})
- ]);
+ it('should sort array item', () => {
+ const { array } = createArrayFormWith2Items();
- const array = contentForm.get(complexSchema.fields[3])!.get(languages[0]) as FieldArrayForm;
+ array.sort([array.get(1), array.get(0)]);
+
+ expectLength(array, 2);
+ expect(array.form.value).toEqual([{ nested41: 'Text2' }, { nested41: 'Text1' }]);
+ });
+
+ it('should remove array item', () => {
+ const { array } = createArrayFormWith2Items();
- array.addItem();
- array.addItem();
array.removeItemAt(0);
- const nestedForm = contentForm.form.get('field4.iv') as FormArray;
+ expectLength(array, 1);
+ expect(array.form.value).toEqual([{ nested41: 'Text2' }]);
+ });
+
+ it('should reset array item', () => {
+ const { array } = createArrayFormWith2Items();
+
+ array.reset();
- expect(nestedForm.controls.length).toBe(1);
+ expectLength(array, 0);
+ expect(array.form.value).toEqual([]);
+ });
+
+ it('should unset array item', () => {
+ const { array } = createArrayFormWith2Items();
+
+ array.unset();
+
+ expectLength(array, 0);
+ expect(array.form.value).toEqual(undefined);
});
it('should not array item if field has no nested fields', () => {
const contentForm = createForm([
createField({ id: 4, properties: createProperties('Array'), partitioning: 'invariant' })
]);
+
const nestedForm = contentForm.form.get('field4.iv') as FormArray;
expect(nestedForm.controls.length).toBe(0);
});
+ function createArrayFormWith2Items() {
+ const contentForm = createForm([
+ createField({ id: 4, properties: createProperties('Array'), partitioning: 'invariant', nested: [
+ createNestedField({ id: 41, properties: createProperties('String') }),
+ createNestedField({ id: 42, properties: createProperties('String', { defaultValue: 'Default' }), isDisabled: true })
+ ]})
+ ]);
+
+ const array = contentForm.get('field4')!.get('iv') as FieldArrayForm;
+
+ contentForm.load({
+ field4: {
+ iv: [{
+ nested41: 'Text1'
+ }, {
+ nested41: 'Text2'
+ }]
+ }
+ });
+
+ return { contentForm, array };
+ }
+
+ function expectLength(array: FieldArrayForm, length: number) {
+ expect(array.form.controls.length).toBe(length);
+ expect(array.items.length).toBe(length);
+ }
+
function expectForm(parent: AbstractControl, path: string, test: { invalid?: boolean, disabled?: boolean, value?: any }) {
const form = parent.get(path);
@@ -1110,46 +656,4 @@ describe('ContentForm', () => {
return new EditContentForm(languages,
createSchema({ fields, fieldRules }), {}, 0);
}
-});
-
-type SchemaValues = {
- id?: number;
- fields?: ReadonlyArray;
- fieldsInLists?: ReadonlyArray;
- fieldsInReferences?: ReadonlyArray;
- fieldRules?: ReadonlyArray;
- properties?: SchemaPropertiesDto;
-};
-
-function createSchema({ properties, id, fields, fieldsInLists, fieldsInReferences, fieldRules }: SchemaValues = {}) {
- id = id || 1;
-
- return new SchemaDetailsDto({},
- `schema${1}`,
- `schema${1}`,
- 'category',
- properties || new SchemaPropertiesDto(), false, true,
- creation,
- creator,
- modified,
- modifier,
- new Version('1'),
- fields,
- fieldsInLists || [],
- fieldsInReferences || [],
- fieldRules || []);
-}
-
-type FieldValues = { properties: FieldPropertiesDto; id?: number; partitioning?: string; isDisabled?: boolean, nested?: ReadonlyArray };
-
-function createField({ properties, id, partitioning, isDisabled, nested }: FieldValues) {
- id = id || 1;
-
- return new RootFieldDto({}, id, `field${id}`, properties, partitioning || 'language', false, false, isDisabled, nested);
-}
-
-function createNestedField({ properties, id, isDisabled }: FieldValues) {
- id = id || 1;
-
- return new NestedFieldDto({}, id, `nested${id}`, properties, 0, false, false, isDisabled);
-}
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/frontend/app/shared/state/contents.forms.ts b/frontend/app/shared/state/contents.forms.ts
index ed534bb32..32dcca927 100644
--- a/frontend/app/shared/state/contents.forms.ts
+++ b/frontend/app/shared/state/contents.forms.ts
@@ -7,8 +7,8 @@
// tslint:disable: readonly-array
-import { FormArray, FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
-import { Form, Types, valueAll$ } from '@app/framework';
+import { FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
+import { Form, Types, UndefinableFormArray, valueAll$ } from '@app/framework';
import { BehaviorSubject, Observable } from 'rxjs';
import { debounceTime, onErrorResumeNext } from 'rxjs/operators';
import { AppLanguageDto } from './../services/app-languages.service';
@@ -314,7 +314,7 @@ export class FieldValueForm extends AbstractContentForm {
+export class FieldArrayForm extends AbstractContentForm {
private readonly item$ = new BehaviorSubject>([]);
public get itemChanges(): Observable> {
@@ -349,6 +349,20 @@ export class FieldArrayForm extends AbstractContentForm
this.form.push(child.form);
}
+ public unset() {
+ this.items = [];
+
+ super.unset();
+
+ this.form.clear();
+ }
+
+ public reset() {
+ this.items = [];
+
+ this.form.clear();
+ }
+
public removeItemAt(index: number) {
this.items = this.items.filter((_, i) => i !== index);
@@ -393,7 +407,7 @@ export class FieldArrayForm extends AbstractContentForm
private static buildControl(field: RootFieldDto, isOptional: boolean) {
const validators = FieldsValidators.create(field, isOptional);
- return new FormArray([], validators);
+ return new UndefinableFormArray([], validators);
}
}
diff --git a/frontend/app/shared/state/contents.forms.visitors.spec.ts b/frontend/app/shared/state/contents.forms.visitors.spec.ts
new file mode 100644
index 000000000..811db467e
--- /dev/null
+++ b/frontend/app/shared/state/contents.forms.visitors.spec.ts
@@ -0,0 +1,499 @@
+/*
+ * Squidex Headless CMS
+ *
+ * @license
+ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
+ */
+
+// tslint:disable: max-line-length
+
+import { DateHelper } from '@app/framework';
+import { createProperties, DateTime, FieldDefaultValue, FieldFormatter, FieldsValidators, HtmlValue, MetaFields, SchemaPropertiesDto } from '@app/shared/internal';
+import { TestValues } from './_test-helpers';
+
+const {
+ createField,
+ createSchema
+} = TestValues;
+
+const now = DateTime.parseISO('2017-10-12T16:30:10Z');
+
+describe('SchemaDetailsDto', () => {
+ const field1 = createField({ properties: createProperties('Array'), id: 1 });
+ const field2 = createField({ properties: createProperties('Array'), id: 2 });
+ const field3 = createField({ properties: createProperties('Array'), id: 3 });
+
+ it('should return label as display name', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto('Label') });
+
+ expect(schema.displayName).toBe('Label');
+ });
+
+ it('should return name as display name if label is undefined', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto(undefined) });
+
+ expect(schema.displayName).toBe('schema1');
+ });
+
+ it('should return name as display name label is empty', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto('') });
+
+ expect(schema.displayName).toBe('schema1');
+ });
+
+ it('should return configured fields as list fields if fields are declared', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3], fieldsInLists: ['field1', 'field3'] });
+
+ expect(schema.defaultListFields).toEqual([field1, field3]);
+ });
+
+ it('should return first fields as list fields if no field is declared', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3] });
+
+ expect(schema.defaultListFields).toEqual([MetaFields.lastModifiedByAvatar, field1, MetaFields.statusColor, MetaFields.lastModified]);
+ });
+
+ it('should return preset with empty content field as list fields if fields is empty', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto() });
+
+ expect(schema.defaultListFields).toEqual([MetaFields.lastModifiedByAvatar, '', MetaFields.statusColor, MetaFields.lastModified]);
+ });
+
+ it('should return configured fields as references fields if fields are declared', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3], fieldsInReferences: ['field1', 'field3'] });
+
+ expect(schema.defaultReferenceFields).toEqual([field1, field3]);
+ });
+
+ it('should return first field as reference fields if no field is declared', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3] });
+
+ expect(schema.defaultReferenceFields).toEqual([field1]);
+ });
+
+ it('should return noop field as reference field if list is empty', () => {
+ const schema = createSchema({ properties: new SchemaPropertiesDto() });
+
+ expect(schema.defaultReferenceFields).toEqual(['']);
+ });
+});
+
+describe('FieldDto', () => {
+ it('should return label as display name', () => {
+ const field = createField({ properties: createProperties('Array', { label: 'Label' }) });
+
+ expect(field.displayName).toBe('Label');
+ });
+
+ it('should return name as display name if label is null', () => {
+ const field = createField({ properties: createProperties('Assets') });
+
+ expect(field.displayName).toBe('field1');
+ });
+
+ it('should return name as display name label is empty', () => {
+ const field = createField({ properties: createProperties('Assets', { label: '' }) });
+
+ expect(field.displayName).toBe('field1');
+ });
+
+ it('should return placeholder as display placeholder', () => {
+ const field = createField({ properties: createProperties('Assets', { placeholder: 'Placeholder' }) });
+
+ expect(field.displayPlaceholder).toBe('Placeholder');
+ });
+
+ it('should return empty as display placeholder if placeholder is null', () => {
+ const field = createField({ properties: createProperties('Assets') });
+
+ expect(field.displayPlaceholder).toBe('');
+ });
+
+ it('should return localizable if partitioning is language', () => {
+ const field = createField({ properties: createProperties('Assets'), partitioning: 'language' });
+
+ expect(field.isLocalizable).toBeTruthy();
+ });
+
+ it('should not return localizable if partitioning is invariant', () => {
+ const field = createField({ properties: createProperties('Assets'), partitioning: 'invariant' });
+
+ expect(field.isLocalizable).toBeFalsy();
+ });
+});
+
+describe('ArrayField', () => {
+ const field = createField({ properties: createProperties('Array', { isRequired: true, minItems: 1, maxItems: 5 }) });
+
+ it('should create validators', () => {
+ expect(FieldsValidators.create(field, false).length).toBe(2);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(FieldFormatter.format(field, null)).toBe('');
+ });
+
+ it('should format to plural count for many items', () => {
+ expect(FieldFormatter.format(field, [1, 2, 3])).toBe('3 Items');
+ });
+
+ it('should format to plural count for single item', () => {
+ expect(FieldFormatter.format(field, [1])).toBe('1 Item');
+ });
+
+ it('should return zero formatting if other type', () => {
+ expect(FieldFormatter.format(field, 1)).toBe('0 Items');
+ });
+
+ it('should return default value as null', () => {
+ expect(FieldDefaultValue.get(field, 'iv')).toBeNull();
+ });
+});
+
+describe('AssetsField', () => {
+ const field = createField({ properties: createProperties('Assets', { isRequired: true, minItems: 1, maxItems: 5 }) });
+
+ it('should create validators', () => {
+ expect(FieldsValidators.create(field, false).length).toBe(3);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(FieldFormatter.format(field, null)).toBe('');
+ });
+
+ it('should format to plural count for many items', () => {
+ expect(FieldFormatter.format(field, [1, 2, 3])).toBe('3 Assets');
+ });
+
+ it('should format to plural count for single item', () => {
+ expect(FieldFormatter.format(field, [1])).toBe('1 Asset');
+ });
+
+ it('should return zero formatting if other type', () => {
+ expect(FieldFormatter.format(field, 1)).toBe('0 Assets');
+ });
+
+ it('should return default value from properties', () => {
+ const field2 = createField({ properties: createProperties('Assets', { defaultValue: ['1', '2'] }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toEqual(['1', '2']);
+ });
+
+ it('should override default value from localizable properties', () => {
+ const field2 = createField({ properties: createProperties('Assets', { defaultValue: ['1', '2'], defaultValues: { 'iv': null } }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
+ });
+});
+
+describe('TagsField', () => {
+ const field = createField({ properties: createProperties('Tags', { isRequired: true, minItems: 1, maxItems: 5 }) });
+
+ it('should create validators', () => {
+ expect(FieldsValidators.create(field, false).length).toBe(2);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(FieldFormatter.format(field, null)).toBe('');
+ });
+
+ it('should format to asset count', () => {
+ expect(FieldFormatter.format(field, ['hello', 'squidex', 'cms'])).toBe('hello, squidex, cms');
+ });
+
+ it('should return zero formatting if other type', () => {
+ expect(FieldFormatter.format(field, 1)).toBe('');
+ });
+
+ it('should return default value from properties', () => {
+ const field2 = createField({ properties: createProperties('Tags', { defaultValue: ['1', '2'] }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toEqual(['1', '2']);
+ });
+
+ it('should override default value from localizable properties', () => {
+ const field2 = createField({ properties: createProperties('Tags', { defaultValue: ['1', '2'], defaultValues: { 'iv': null } }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
+ });
+});
+
+describe('BooleanField', () => {
+ const field = createField({ properties: createProperties('Boolean', { editor: 'Checkbox', isRequired: true }) });
+
+ it('should create validators', () => {
+ expect(FieldsValidators.create(field, false).length).toBe(1);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(FieldFormatter.format(field, null)).toBe('');
+ });
+
+ it('should format to Yes if true', () => {
+ expect(FieldFormatter.format(field, true)).toBe('Yes');
+ });
+
+ it('should format to No if false', () => {
+ expect(FieldFormatter.format(field, false)).toBe('No');
+ });
+
+ it('should return default value from properties', () => {
+ const field2 = createField({ properties: createProperties('Boolean', { editor: 'Checkbox', defaultValue: true }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toBeTruthy();
+ });
+
+ it('should override default value from localizable properties', () => {
+ const field2 = createField({ properties: createProperties('Boolean', { defaultValue: true, defaultValues: { 'iv': null } }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
+ });
+});
+
+describe('DateTimeField', () => {
+ const field = createField({ properties: createProperties('DateTime', { editor: 'DateTime', isRequired: true }) });
+
+ beforeEach(() => {
+ DateHelper.setlocale(null);
+ });
+
+ it('should create validators', () => {
+ expect(FieldsValidators.create(field, false).length).toBe(1);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(FieldFormatter.format(field, null)).toBe('');
+ });
+
+ it('should format to input if parsing failed', () => {
+ expect(FieldFormatter.format(field, true)).toBe(true);
+ });
+
+ it('should format old format to date', () => {
+ const dateField = createField({ properties: createProperties('DateTime', { editor: 'Date' }) });
+
+ expect(FieldFormatter.format(dateField, '2017-12-12')).toBe('12/12/2017');
+ });
+
+ it('should format datetime to date', () => {
+ const dateField = createField({ properties: createProperties('DateTime', { editor: 'Date' }) });
+
+ expect(FieldFormatter.format(dateField, '2017-12-12T16:00:00Z')).toBe('12/12/2017');
+ });
+
+ it('should format date to date', () => {
+ const dateField = createField({ properties: createProperties('DateTime', { editor: 'Date' }) });
+
+ expect(FieldFormatter.format(dateField, '2017-12-12T00:00:00Z')).toBe('12/12/2017');
+ });
+
+ it('should format to date time', () => {
+ const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime' }) });
+
+ expect(FieldFormatter.format(field2, '2017-12-12T16:00:00Z')).toBe('12/12/2017, 4:00:00 PM');
+ });
+
+ it('should return default from properties value', () => {
+ const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime', defaultValue: '2017-10-12T16:00:00Z' }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toEqual('2017-10-12T16:00:00Z');
+ });
+
+ it('should override default value from localizable properties', () => {
+ const field2 = createField({ properties: createProperties('DateTime', { defaultValue: '2017-10-12T16:00:00Z', defaultValues: { 'iv': null } }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
+ });
+
+ it('should return default from Today', () => {
+ const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime', calculatedDefaultValue: 'Today' }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv', now)).toEqual('2017-10-12T00:00:00Z');
+ });
+
+ it('should return default value from Today', () => {
+ const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime', calculatedDefaultValue: 'Now' }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv', now)).toEqual('2017-10-12T16:30:10Z');
+ });
+});
+
+describe('GeolocationField', () => {
+ const field = createField({ properties: createProperties('Geolocation', { isRequired: true }) });
+
+ it('should create validators', () => {
+ expect(FieldsValidators.create(field, false).length).toBe(1);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(FieldFormatter.format(field, null)).toBe('');
+ });
+
+ it('should format to latitude and longitude', () => {
+ expect(FieldFormatter.format(field, { latitude: 42, longitude: 3.14 })).toBe('3.14, 42');
+ });
+
+ it('should return default value as null', () => {
+ expect(FieldDefaultValue.get(field, 'iv')).toBeNull();
+ });
+});
+
+describe('JsonField', () => {
+ const field = createField({ properties: createProperties('Json', { isRequired: true }) });
+
+ it('should create validators', () => {
+ expect(FieldsValidators.create(field, false).length).toBe(1);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(FieldFormatter.format(field, null)).toBe('');
+ });
+
+ it('should format to constant', () => {
+ expect(FieldFormatter.format(field, {})).toBe('');
+ });
+
+ it('should return default value as null', () => {
+ expect(FieldDefaultValue.get(field, 'iv')).toBeNull();
+ });
+});
+
+describe('NumberField', () => {
+ const field = createField({ properties: createProperties('Number', { isRequired: true, minValue: 1, maxValue: 6, allowedValues: [1, 3] }) });
+
+ it('should create validators', () => {
+ expect(FieldsValidators.create(field, false).length).toBe(3);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(FieldFormatter.format(field, null)).toBe('');
+ });
+
+ it('should format to number', () => {
+ expect(FieldFormatter.format(field, 42)).toEqual('42');
+ });
+
+ it('should format to stars if html allowed', () => {
+ const field2 = createField({ properties: createProperties('Number', { editor: 'Stars' }) });
+
+ expect(FieldFormatter.format(field2, 3)).toEqual(new HtmlValue('★ ★ ★ '));
+ });
+
+ it('should format to short star view for many stars', () => {
+ const field2 = createField({ properties: createProperties('Number', { editor: 'Stars' }) });
+
+ expect(FieldFormatter.format(field2, 42)).toEqual(new HtmlValue('★ 42'));
+ });
+
+ it('should format to short star view for no stars', () => {
+ const field2 = createField({ properties: createProperties('Number', { editor: 'Stars' }) });
+
+ expect(FieldFormatter.format(field2, 0)).toEqual(new HtmlValue('★ 0'));
+ });
+
+ it('should format to short star view for negative stars', () => {
+ const field2 = createField({ properties: createProperties('Number', { editor: 'Stars' }) });
+
+ expect(FieldFormatter.format(field2, -13)).toEqual(new HtmlValue('★ -13'));
+ });
+
+ it('should not format to stars if html not allowed', () => {
+ const field2 = createField({ properties: createProperties('Number', { editor: 'Stars' }) });
+
+ expect(FieldFormatter.format(field2, 3, false)).toEqual('3');
+ });
+
+ it('should return default value from properties', () => {
+ const field2 = createField({ properties: createProperties('Number', { defaultValue: 13 }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toEqual(13);
+ });
+
+ it('should override default value from localizable properties', () => {
+ const field2 = createField({ properties: createProperties('Number', { defaultValue: 13, defaultValues: { 'iv': null } }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
+ });
+});
+
+describe('ReferencesField', () => {
+ const field = createField({ properties: createProperties('References', { editor: 'List', isRequired: true, minItems: 1, maxItems: 5 }) });
+
+ it('should create validators', () => {
+ expect(FieldsValidators.create(field, false).length).toBe(3);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(FieldFormatter.format(field, null)).toBe('');
+ });
+
+ it('should format to plural count for many items', () => {
+ expect(FieldFormatter.format(field, [1, 2, 3])).toBe('3 References');
+ });
+
+ it('should format to plural count for single item', () => {
+ expect(FieldFormatter.format(field, [1])).toBe('1 Reference');
+ });
+
+ it('should return zero formatting if other type', () => {
+ expect(FieldFormatter.format(field, 1)).toBe('0 References');
+ });
+
+ it('should return default value from properties', () => {
+ const field2 = createField({ properties: createProperties('References', { defaultValue: ['1', '2'] }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toEqual(['1', '2']);
+ });
+
+ it('should override default value from localizable properties', () => {
+ const field2 = createField({ properties: createProperties('References', { defaultValue: ['1', '2'], defaultValues: { 'iv': null } }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
+ });
+});
+
+describe('StringField', () => {
+ const field = createField({ properties: createProperties('String', { isRequired: true, pattern: 'pattern', minLength: 1, maxLength: 5, allowedValues: ['a', 'b'] }) });
+
+ it('should create validators', () => {
+ expect(FieldsValidators.create(field, false).length).toBe(4);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(FieldFormatter.format(field, null)).toBe('');
+ });
+
+ it('should format to string', () => {
+ expect(FieldFormatter.format(field, 'hello')).toBe('hello');
+ });
+
+ it('should format to preview image', () => {
+ const field2 = createField({ properties: createProperties('String', { editor: 'StockPhoto' }) });
+
+ expect(FieldFormatter.format(field2, 'https://images.unsplash.com/123?x', true)).toEqual(new HtmlValue('
'));
+ });
+
+ it('should not format to preview image when html not allowed', () => {
+ const field2 = createField({ properties: createProperties('String', { editor: 'StockPhoto' }) });
+
+ expect(FieldFormatter.format(field2, 'https://images.unsplash.com/123?x', false)).toBe('https://images.unsplash.com/123?x');
+ });
+
+ it('should not format to preview image when not unsplash image', () => {
+ const field2 = createField({ properties: createProperties('String', { editor: 'StockPhoto' }) });
+
+ expect(FieldFormatter.format(field2, 'https://images.com/123?x', true)).toBe('https://images.com/123?x');
+ });
+
+ it('should return default value from properties', () => {
+ const field2 = createField({ properties: createProperties('String', { defaultValue: 'MyDefault' }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toEqual('MyDefault');
+ });
+
+ it('should override default value from localizable properties', () => {
+ const field2 = createField({ properties: createProperties('String', { defaultValue: 'MyDefault', defaultValues: { 'iv': null } }) });
+
+ expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
+ });
+});
\ No newline at end of file
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 2383f89b7..70764b8a3 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1569,7 +1569,7 @@
"dependencies": {
"jsesc": {
"version": "1.3.0",
- "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
"integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
"dev": true
}
@@ -3053,7 +3053,7 @@
},
"css-color-names": {
"version": "0.0.4",
- "resolved": "http://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
+ "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
"integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=",
"dev": true
},
@@ -8755,7 +8755,7 @@
},
"onetime": {
"version": "1.1.0",
- "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"dev": true
},
@@ -12464,7 +12464,7 @@
},
"rgba-regex": {
"version": "1.0.0",
- "resolved": "http://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz",
"integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=",
"dev": true
},
@@ -14412,7 +14412,7 @@
"dependencies": {
"json5": {
"version": "1.0.1",
- "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"dev": true,
"requires": {
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index 38c1498cd..a70a190c5 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -20,7 +20,7 @@
"sourceMap": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
- "target": "es5",
+ "target": "es2015",
"paths": {
"@app*": [
"app*"