Browse Source

Bugfix/clear array (#626)

* Clear array

* Tests
pull/627/head
Sebastian Stehle 5 years ago
committed by GitHub
parent
commit
ade0a3a8a0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      backend/i18n/frontend_en.json
  2. 3
      backend/i18n/frontend_it.json
  3. 3
      backend/i18n/frontend_nl.json
  4. 1
      backend/i18n/source/backend_en.json
  5. 3
      backend/i18n/source/frontend_en.json
  6. 7
      backend/src/Squidex.Shared/Texts.it.resx
  7. 7
      backend/src/Squidex.Shared/Texts.nl.resx
  8. 7
      backend/src/Squidex.Shared/Texts.resx
  9. 8
      frontend/app/features/content/shared/forms/array-editor.component.html
  10. 4
      frontend/app/features/content/shared/forms/array-editor.component.ts
  11. 2
      frontend/app/features/content/shared/forms/field-editor.component.ts
  12. 89
      frontend/app/framework/angular/forms/undefinable-form-array.spec.ts
  13. 97
      frontend/app/framework/angular/forms/undefinable-form-array.ts
  14. 1
      frontend/app/framework/declarations.ts
  15. 119
      frontend/app/shared/services/schemas.spec.ts
  16. 54
      frontend/app/shared/state/_test-helpers.ts
  17. 4
      frontend/app/shared/state/contents.forms-helpers.ts
  18. 632
      frontend/app/shared/state/contents.forms.spec.ts
  19. 22
      frontend/app/shared/state/contents.forms.ts
  20. 499
      frontend/app/shared/state/contents.forms.visitors.spec.ts
  21. 10
      frontend/package-lock.json
  22. 2
      frontend/tsconfig.json

3
backend/i18n/frontend_en.json

@ -340,6 +340,9 @@
"common.workflows": "Workflows",
"common.yes": "Yes",
"contents.arrayAddItem": "Add Item",
"contents.arrayClear": "Clear",
"contents.arrayClearConfirmText": "Do you really want to clear the array?",
"contents.arrayClearConfirmTitle": "Clear array",
"contents.arrayCloneItem": "Clone this item",
"contents.arrayCollapseAll": "Collapse all items",
"contents.arrayCollapseItem": "Collapse this item",

3
backend/i18n/frontend_it.json

@ -340,6 +340,9 @@
"common.workflows": "Workflow",
"common.yes": "Si",
"contents.arrayAddItem": "Aggiungi un elemento",
"contents.arrayClear": "Clear",
"contents.arrayClearConfirmText": "Do you really want to clear the array?",
"contents.arrayClearConfirmTitle": "Clear array",
"contents.arrayCloneItem": "Clona questo elemento",
"contents.arrayCollapseAll": "Comprimi tutti gli elementi",
"contents.arrayCollapseItem": "Comprimi l'elemento",

3
backend/i18n/frontend_nl.json

@ -340,6 +340,9 @@
"common.workflows": "Workflows",
"common.yes": "Ja",
"contents.arrayAddItem": "Item toevoegen",
"contents.arrayClear": "Clear",
"contents.arrayClearConfirmText": "Do you really want to clear the array?",
"contents.arrayClearConfirmTitle": "Clear array",
"contents.arrayCloneItem": "Kloon dit item",
"contents.arrayCollapseAll": "Collapse all items",
"contents.arrayCollapseItem": "Collapse this item",

1
backend/i18n/source/backend_en.json

@ -172,6 +172,7 @@
"contents.validation.normalCharactersBetween": "Must have between {min} and {max} text character(s).",
"contents.validation.notAllowed": "Not an allowed value.",
"contents.validation.pattern": "Must follow the pattern.",
"contents.validation.reference": "Geolocation can only have latitude and longitude property.",
"contents.validation.referenceNotFound": "Reference '{id}' not found.",
"contents.validation.referenceToInvalidSchema": "Reference '{id}' has invalid schema.",
"contents.validation.regexTooSlow": "Regex is too slow.",

3
backend/i18n/source/frontend_en.json

@ -340,6 +340,9 @@
"common.workflows": "Workflows",
"common.yes": "Yes",
"contents.arrayAddItem": "Add Item",
"contents.arrayClear": "Clear",
"contents.arrayClearConfirmText": "Do you really want to clear the array?",
"contents.arrayClearConfirmTitle": "Clear array",
"contents.arrayCloneItem": "Clone this item",
"contents.arrayCollapseAll": "Collapse all items",
"contents.arrayCollapseItem": "Collapse this item",

7
backend/src/Squidex.Shared/Texts.it.resx

@ -601,6 +601,9 @@
<data name="contents.validation.pattern" xml:space="preserve">
<value>Deve seguire il pattern.</value>
</data>
<data name="contents.validation.reference" xml:space="preserve">
<value>Geolocation can only have latitude and longitude property.</value>
</data>
<data name="contents.validation.referenceNotFound" xml:space="preserve">
<value>Contiene un collegamento '{id}' non valido.</value>
</data>
@ -938,10 +941,10 @@
<value>Sebastian Stehle and Contributors, 2016-2021</value>
</data>
<data name="setup.ruleAppCreation.warningAdmins" xml:space="preserve">
<value>With your setup, only admins can create new apps. If you want to change this set &lt;code&gt;UI__ONLYADMINCANCREATEAPPS=false&lt;/code&gt; as environment variable.</value>
<value>With your setup, only admins can create new apps. If you want to change this set &lt;code&gt;UI__ONLYADMINSCANCREATEAPPS=false&lt;/code&gt; as environment variable.</value>
</data>
<data name="setup.ruleAppCreation.warningAll" xml:space="preserve">
<value>With your setup, every user can create new apps. If you want to change this set &lt;code&gt;UI__ONLYADMINCANCREATEAPPS=true&lt;/code&gt; as environment variable.</value>
<value>With your setup, every user can create new apps. If you want to change this set &lt;code&gt;UI__ONLYADMINSCANCREATEAPPS=true&lt;/code&gt; as environment variable.</value>
</data>
<data name="setup.ruleFolder.warning" xml:space="preserve">
<value>You are using the &lt;strong&gt;folder asset store&lt;/strong&gt; where all assets are stored in the file system. Please remember to include the asset folder into your backup strategy and map it to a volume, if you are using Docker.</value>

7
backend/src/Squidex.Shared/Texts.nl.resx

@ -601,6 +601,9 @@
<data name="contents.validation.pattern" xml:space="preserve">
<value>Moet het patroon volgen.</value>
</data>
<data name="contents.validation.reference" xml:space="preserve">
<value>Geolocation can only have latitude and longitude property.</value>
</data>
<data name="contents.validation.referenceNotFound" xml:space="preserve">
<value>Bevat ongeldige referentie '{id}'.</value>
</data>
@ -938,10 +941,10 @@
<value>Sebastian Stehle and Contributors, 2016-2021</value>
</data>
<data name="setup.ruleAppCreation.warningAdmins" xml:space="preserve">
<value>With your setup, only admins can create new apps. If you want to change this set &lt;code&gt;UI__ONLYADMINCANCREATEAPPS=false&lt;/code&gt; as environment variable.</value>
<value>With your setup, only admins can create new apps. If you want to change this set &lt;code&gt;UI__ONLYADMINSCANCREATEAPPS=false&lt;/code&gt; as environment variable.</value>
</data>
<data name="setup.ruleAppCreation.warningAll" xml:space="preserve">
<value>With your setup, every user can create new apps. If you want to change this set &lt;code&gt;UI__ONLYADMINCANCREATEAPPS=true&lt;/code&gt; as environment variable.</value>
<value>With your setup, every user can create new apps. If you want to change this set &lt;code&gt;UI__ONLYADMINSCANCREATEAPPS=true&lt;/code&gt; as environment variable.</value>
</data>
<data name="setup.ruleFolder.warning" xml:space="preserve">
<value>You are using the &lt;strong&gt;folder asset store&lt;/strong&gt; where all assets are stored in the file system. Please remember to include the asset folder into your backup strategy and map it to a volume, if you are using Docker.</value>

7
backend/src/Squidex.Shared/Texts.resx

@ -601,6 +601,9 @@
<data name="contents.validation.pattern" xml:space="preserve">
<value>Must follow the pattern.</value>
</data>
<data name="contents.validation.reference" xml:space="preserve">
<value>Geolocation can only have latitude and longitude property.</value>
</data>
<data name="contents.validation.referenceNotFound" xml:space="preserve">
<value>Reference '{id}' not found.</value>
</data>
@ -938,10 +941,10 @@
<value>Sebastian Stehle and Contributors, 2016-2021</value>
</data>
<data name="setup.ruleAppCreation.warningAdmins" xml:space="preserve">
<value>With your setup, only admins can create new apps. If you want to change this set &lt;code&gt;UI__ONLYADMINCANCREATEAPPS=false&lt;/code&gt; as environment variable.</value>
<value>With your setup, only admins can create new apps. If you want to change this set &lt;code&gt;UI__ONLYADMINSCANCREATEAPPS=false&lt;/code&gt; as environment variable.</value>
</data>
<data name="setup.ruleAppCreation.warningAll" xml:space="preserve">
<value>With your setup, every user can create new apps. If you want to change this set &lt;code&gt;UI__ONLYADMINCANCREATEAPPS=true&lt;/code&gt; as environment variable.</value>
<value>With your setup, every user can create new apps. If you want to change this set &lt;code&gt;UI__ONLYADMINSCANCREATEAPPS=true&lt;/code&gt; as environment variable.</value>
</data>
<data name="setup.ruleFolder.warning" xml:space="preserve">
<value>You are using the &lt;strong&gt;folder asset store&lt;/strong&gt; where all assets are stored in the file system. Please remember to include the asset folder into your backup strategy and map it to a volume, if you are using Docker.</value>

8
frontend/app/features/content/shared/forms/array-editor.component.html

@ -32,6 +32,14 @@
<button type="button" class="btn btn-outline-success" [disabled]="isFull | async" (click)="addItem()">
{{ 'contents.arrayAddItem' | sqxTranslate }}
</button>
<button type="button" class="btn btn-text-danger ml-2"
(sqxConfirmClick)="clear()"
confirmTitle="i18n:contents.arrayClearConfirmTitle"
confirmText="i18n:contents.arrayClearConfirmText"
confirmRememberKey="leaveApp">
{{ 'contents.arrayClear' | sqxTranslate }}
</button>
</div>
<div class="col-auto" *ngIf="items.length > 0">

4
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<ReadonlyArray<FieldArrayItemForm>>) {
this.formModel.sort(sorted(event));

2
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();
}
}

89
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);
}
});

97
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<any>).emit(this.value);
(this.statusChanges as EventEmitter<string>).emit(this.status);
}
if (this.parent && !opts.onlySelf) {
this.parent.updateValueAndValidity(opts);
}
}
private unsetValue() {
(this as { value: any }).value = undefined;
}
}

1
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';

119
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();
});
});

54
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>();
authService.setup(x => x.user)
.returns(() => <any>{ id: modifier, token: modifier });
type SchemaValues = {
id?: number;
fields?: ReadonlyArray<RootFieldDto>;
fieldsInLists?: ReadonlyArray<string>;
fieldsInReferences?: ReadonlyArray<string>;
fieldRules?: ReadonlyArray<FieldRule>;
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<NestedFieldDto>
};
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,

4
frontend/app/shared/state/contents.forms-helpers.ts

@ -164,6 +164,10 @@ export abstract class AbstractContentForm<T extends FieldDto, TForm extends Abst
this.updateCustomState(context, state);
}
public unset() {
this.form.setValue(undefined);
}
protected updateCustomState(_context: RuleContext, _state: AbstractContentFormState) {
return;
}

632
frontend/app/shared/state/contents.forms.spec.ts

@ -8,502 +8,18 @@
// tslint:disable: max-line-length
import { AbstractControl, FormArray } from '@angular/forms';
import { DateHelper } from '@app/framework';
import { AppLanguageDto, createProperties, DateTime, EditContentForm, FieldDefaultValue, FieldFormatter, FieldPropertiesDto, FieldsValidators, getContentValue, HtmlValue, LanguageDto, MetaFields, NestedFieldDto, RootFieldDto, SchemaDetailsDto, SchemaPropertiesDto, Version } from '@app/shared/internal';
import { AppLanguageDto, createProperties, EditContentForm, getContentValue, HtmlValue, LanguageDto, RootFieldDto } from '@app/shared/internal';
import { FieldRule } from './../services/schemas.service';
import { FieldArrayForm } from './contents.forms';
import { PartitionConfig } from './contents.forms-helpers';
import { TestValues } from './_test-helpers';
const {
modified,
modifier,
creation,
creator
createField,
createNestedField,
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 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('<Json />');
});
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('&#9733; &#9733; &#9733; '));
});
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('&#9733; 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('&#9733; 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('&#9733; -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('<img src="https://images.unsplash.com/123?x&q=80&fm=jpg&crop=entropy&cs=tinysrgb&h=50&fit=max" />'));
});
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 } = createArrayFormWith2Items();
const array = contentForm.get(complexSchema.fields[3])!.get(languages[0]) as FieldArrayForm;
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();
expect(nestedForm.controls.length).toBe(1);
array.reset();
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);
@ -1111,45 +657,3 @@ describe('ContentForm', () => {
createSchema({ fields, fieldRules }), {}, 0);
}
});
type SchemaValues = {
id?: number;
fields?: ReadonlyArray<RootFieldDto>;
fieldsInLists?: ReadonlyArray<string>;
fieldsInReferences?: ReadonlyArray<string>;
fieldRules?: ReadonlyArray<FieldRule>;
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<NestedFieldDto> };
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);
}

22
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<RootFieldDto, FormContro
}
}
export class FieldArrayForm extends AbstractContentForm<RootFieldDto, FormArray> {
export class FieldArrayForm extends AbstractContentForm<RootFieldDto, UndefinableFormArray> {
private readonly item$ = new BehaviorSubject<ReadonlyArray<FieldArrayItemForm>>([]);
public get itemChanges(): Observable<ReadonlyArray<FieldArrayItemForm>> {
@ -349,6 +349,20 @@ export class FieldArrayForm extends AbstractContentForm<RootFieldDto, FormArray>
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<RootFieldDto, FormArray>
private static buildControl(field: RootFieldDto, isOptional: boolean) {
const validators = FieldsValidators.create(field, isOptional);
return new FormArray([], validators);
return new UndefinableFormArray([], validators);
}
}

499
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('<Json />');
});
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('&#9733; &#9733; &#9733; '));
});
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('&#9733; 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('&#9733; 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('&#9733; -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('<img src="https://images.unsplash.com/123?x&q=80&fm=jpg&crop=entropy&cs=tinysrgb&h=50&fit=max" />'));
});
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();
});
});

10
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": {

2
frontend/tsconfig.json

@ -20,7 +20,7 @@
"sourceMap": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5",
"target": "es2015",
"paths": {
"@app*": [
"app*"

Loading…
Cancel
Save