Browse Source

Tests

pull/1/head
Sebastian 9 years ago
parent
commit
17396976e0
  1. 5
      src/Squidex.Core/Schemas/NamedElementProperties.cs
  2. 23
      src/Squidex.Core/Schemas/NumberFieldProperties.cs
  3. 24
      src/Squidex.Core/Schemas/StringFieldProperties.cs
  4. 4
      src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs
  5. 4
      src/Squidex.Infrastructure/CQRS/Events/EventDataFormatter.cs
  6. 16
      src/Squidex.Infrastructure/EnumExtensions.cs
  7. 10
      src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs
  8. 6
      src/Squidex/app/features/content/pages/content/content-page.component.html
  9. 7
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  10. 4
      src/Squidex/app/features/schemas/pages/schema/field.component.html
  11. 4
      src/Squidex/app/features/schemas/pages/schema/field.component.ts
  12. 4
      src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.html
  13. 4
      src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.ts
  14. 2
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  15. 5
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  16. 2
      src/Squidex/app/features/schemas/pages/schema/types/boolean-ui.component.html
  17. 2
      src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.html
  18. 2
      src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.html
  19. 2
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html
  20. 4
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts
  21. 2
      src/Squidex/app/features/settings/pages/clients/client.component.html
  22. 2
      src/Squidex/app/features/settings/pages/clients/clients-page.component.html
  23. 92
      src/Squidex/app/framework/angular/control-errors.component.ts
  24. 14
      src/Squidex/app/framework/angular/validators.ts
  25. 2
      src/Squidex/app/shared/components/app-form.component.html
  26. 6
      src/Squidex/app/shared/components/app-form.component.ts
  27. 8
      tests/Squidex.Core.Tests/Schemas/NumberFieldPropertiesTests.cs
  28. 29
      tests/Squidex.Core.Tests/Schemas/StringFieldPropertiesTests.cs
  29. 168
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/DefaultDomainObjectRepositoryTests.cs

5
src/Squidex.Core/Schemas/NamedElementProperties.cs

@ -47,6 +47,11 @@ namespace Squidex.Core.Schemas
}
}
public bool ShouldSerializeIsFrozen()
{
return false;
}
public void Freeze()
{
IsFrozen = true;

23
src/Squidex.Core/Schemas/NumberFieldProperties.cs

@ -93,27 +93,22 @@ namespace Squidex.Core.Schemas
yield return new ValidationError("Max value must be greater than min value", nameof(MinValue), nameof(MaxValue));
}
if (AllowedValues != null && AllowedValues.Count > 0 && (MinValue.HasValue || MaxValue.HasValue))
{
yield return new ValidationError("Either or allowed values or range can be defined",
nameof(AllowedValues),
nameof(MinValue),
nameof(MaxValue));
}
if (!DefaultValue.HasValue)
if (DefaultValue.HasValue && MinValue.HasValue && DefaultValue.Value < MinValue.Value)
{
yield break;
yield return new ValidationError("Default value must be greater than min value", nameof(DefaultValue));
}
if (MinValue.HasValue && DefaultValue.Value < MinValue.Value)
if (DefaultValue.HasValue && MaxValue.HasValue && DefaultValue.Value > MaxValue.Value)
{
yield return new ValidationError("Default value must be greater than min value", nameof(DefaultValue));
yield return new ValidationError("Default value must be less than max value", nameof(DefaultValue));
}
if (MaxValue.HasValue && DefaultValue.Value > MaxValue.Value)
if (AllowedValues != null && AllowedValues.Count > 0 && (MinValue.HasValue || MaxValue.HasValue))
{
yield return new ValidationError("Default value must be less than max value", nameof(DefaultValue));
yield return new ValidationError("Either allowed values or min and max value can be defined",
nameof(AllowedValues),
nameof(MinValue),
nameof(MaxValue));
}
}
}

24
src/Squidex.Core/Schemas/StringFieldProperties.cs

@ -6,9 +6,7 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Squidex.Infrastructure;
using System.Collections.Immutable;
// ReSharper disable ObjectCreationAsStatement
@ -108,25 +106,17 @@ namespace Squidex.Core.Schemas
yield return new ValidationError("Max length must be greater than min length", nameof(MinLength), nameof(MaxLength));
}
if (Pattern == null)
if (Pattern != null && !Pattern.IsValidRegex())
{
yield break;
}
var isValidPattern = true;
try
{
new Regex(Pattern);
}
catch (ArgumentException)
{
isValidPattern = false;
yield return new ValidationError("Pattern is not a valid expression", nameof(Pattern));
}
if (!isValidPattern)
if (AllowedValues != null && AllowedValues.Count > 0 && (MinLength.HasValue || MaxLength.HasValue))
{
yield return new ValidationError("Pattern is not a valid expression", nameof(Pattern));
yield return new ValidationError("Either allowed values or min and max length can be defined",
nameof(AllowedValues),
nameof(MinLength),
nameof(MaxLength));
}
}
}

4
src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs

@ -49,8 +49,6 @@ namespace Squidex.Infrastructure.CQRS.Commands
var streamName = nameResolver.GetStreamName(typeof(TDomainObject), id);
var domainObject = (TDomainObject)factory.CreateNew(typeof(TDomainObject), id);
var events = await eventStore.GetEventsAsync(streamName).ToList();
if (events.Count == 0)
@ -58,6 +56,8 @@ namespace Squidex.Infrastructure.CQRS.Commands
throw new DomainObjectNotFoundException(id.ToString(), typeof(TDomainObject));
}
var domainObject = (TDomainObject)factory.CreateNew(typeof(TDomainObject), id);
foreach (var eventData in events)
{
var envelope = formatter.Parse(eventData);

4
src/Squidex.Infrastructure/CQRS/Events/EventDataFormatter.cs

@ -22,7 +22,7 @@ namespace Squidex.Infrastructure.CQRS.Events
this.serializerSettings = serializerSettings ?? new JsonSerializerSettings();
}
public Envelope<IEvent> Parse(EventData eventData)
public virtual Envelope<IEvent> Parse(EventData eventData)
{
var headers = ReadJson<PropertiesBag>(eventData.Metadata);
@ -34,7 +34,7 @@ namespace Squidex.Infrastructure.CQRS.Events
return envelope;
}
public EventData ToEventData(Envelope<IEvent> envelope, Guid commitId)
public virtual EventData ToEventData(Envelope<IEvent> envelope, Guid commitId)
{
var eventType = TypeNameRegistry.GetName(envelope.Payload.GetType());

16
src/Squidex.Infrastructure/EnumExtensions.cs

@ -7,6 +7,8 @@
// ==========================================================================
using System;
using System.Text.RegularExpressions;
// ReSharper disable ObjectCreationAsStatement
namespace Squidex.Infrastructure
{
@ -23,5 +25,19 @@ namespace Squidex.Infrastructure
return false;
}
}
public static bool IsValidRegex(this string value)
{
try
{
new Regex(value);
return true;
}
catch (ArgumentException)
{
return false;
}
}
}
}

10
src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs

@ -29,6 +29,8 @@ namespace Squidex.Read.Schemas.Services.Implementations
private sealed class CacheItem
{
public ISchemaEntityWithSchema Entity;
public string Name;
}
public CachingSchemaProvider(IMemoryCache cache, ISchemaRepository repository)
@ -48,7 +50,7 @@ namespace Squidex.Read.Schemas.Services.Implementations
{
var entity = await repository.FindSchemaAsync(schemaId);
cacheItem = new CacheItem { Entity = entity };
cacheItem = new CacheItem { Entity = entity, Name = entity?.Name };
Cache.Set(cacheKey, cacheItem, CacheDuration);
@ -72,7 +74,7 @@ namespace Squidex.Read.Schemas.Services.Implementations
{
var entity = await repository.FindSchemaAsync(appId, name);
cacheItem = new CacheItem { Entity = entity };
cacheItem = new CacheItem { Entity = entity, Name = name };
Cache.Set(cacheKey, cacheItem, CacheDuration);
@ -94,9 +96,9 @@ namespace Squidex.Read.Schemas.Services.Implementations
var cacheItem = Cache.Get<CacheItem>(cacheKey);
if (cacheItem?.Entity != null)
if (cacheItem?.Name != null)
{
Cache.Remove(BuildNameCacheKey(@event.Headers.AppId(), cacheItem.Entity.Name));
Cache.Remove(BuildNameCacheKey(@event.Headers.AppId(), cacheItem.Name));
}
Cache.Remove(cacheKey);

6
src/Squidex/app/features/content/pages/content/content-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Content" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<form [formGroup]="contentForm" (ngSubmit)="saveSchema()">
<form [formGroup]="contentForm" (ngSubmit)="saveContent()">
<div class="panel panel-light" >
<div class="panel-header">
<div class="float-xs-right">
@ -22,6 +22,8 @@
<div class="table-items-row">
<label>{{field|displayName:'properties.label':'name'}}</label>
<sqx-control-errors [for]="field.name" fieldName="{{field|displayName:'properties.label':'name'}}" [submitted]="contentFormSubmitted"></sqx-control-errors>
<div>
<div [ngSwitch]="field.properties.fieldType">
<div *ngSwitchCase="'number'">
@ -44,7 +46,7 @@
<div *ngSwitchCase="'string'">
<div [ngSwitch]="field.properties.editor">
<div *ngSwitchCase="'Input'">
<input class="form-control" type="number" [formControlName]="field.name">
<input class="form-control" type="text" [formControlName]="field.name">
</div>
<div *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControlName]="field.name">

7
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -28,6 +28,7 @@ import {
export class ContentPageComponent extends AppComponentBase {
public schema: SchemaDetailsDto;
public contentFormSubmitted = false;
public contentForm: FormGroup;
public isNewMode = false;
@ -50,6 +51,10 @@ export class ContentPageComponent extends AppComponentBase {
});
}
public saveContent() {
this.contentFormSubmitted = true;
}
private setupForm(schema: SchemaDetailsDto) {
const controls: { [key: string]: AbstractControl } = {};
@ -74,7 +79,7 @@ export class ContentPageComponent extends AppComponentBase {
}
}
controls[field.name] = new FormControl(validators);
controls[field.name] = new FormControl(undefined, validators);
}
this.contentForm = new FormGroup(controls);

4
src/Squidex/app/features/schemas/pages/schema/field.component.html

@ -86,7 +86,7 @@
<label for="field-label" class="col-xs-3 col-form-label">Label</label>
<div class="col-xs-6">
<sqx-control-errors for="label" [submitted]="editForm.touched"></sqx-control-errors>
<sqx-control-errors for="label" [submitted]="editFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="field-label" maxlength="100" formControlName="label" />
@ -100,7 +100,7 @@
<label for="field-hints" class="col-xs-3 col-form-label">Hints</label>
<div class="col-xs-6">
<sqx-control-errors for="hints" [submitted]="editForm.touched"></sqx-control-errors>
<sqx-control-errors for="hints" [submitted]="editFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="field-hints" maxlength="100" formControlName="hints" />

4
src/Squidex/app/features/schemas/pages/schema/field.component.ts

@ -54,6 +54,7 @@ export class FieldComponent implements OnInit {
return this.field.properties.label && this.field.properties.label.length > 0 ? this.field.properties.label : this.field.name;
}
public editFormSubmitted = false;
public editForm: FormGroup =
this.formBuilder.group({
label: ['',
@ -77,7 +78,7 @@ export class FieldComponent implements OnInit {
}
public save() {
this.editForm.markAsTouched();
this.editFormSubmitted = true;
if (this.editForm.valid) {
const properties = createProperties(this.field.properties['fieldType'], this.editForm.value);
@ -107,6 +108,7 @@ export class FieldComponent implements OnInit {
}
private resetForm() {
this.editFormSubmitted = false;
this.editForm.reset(this.field.properties);
this.isEditing = false;

4
src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.html

@ -8,7 +8,7 @@
<div class="form-group">
<label for="schema-label">Label</label>
<sqx-control-errors for="label" [submitted]="editForm.touched"></sqx-control-errors>
<sqx-control-errors for="label" [submitted]="editFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="schema-label" formControlName="label" />
</div>
@ -16,7 +16,7 @@
<div class="form-group">
<label for="schema-hints">Hints</label>
<sqx-control-errors for="hints" [submitted]="editForm.touched"></sqx-control-errors>
<sqx-control-errors for="hints" [submitted]="editFormSubmitted"></sqx-control-errors>
<textarea type="text" class="form-control" id="schema-hints" formControlName="hints" rows="4"></textarea>
</div>

4
src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.ts

@ -34,6 +34,7 @@ export class SchemaEditFormComponent implements OnInit {
@Input()
public appName: string;
public editFormSubmitted = false;
public editForm: FormGroup =
this.formBuilder.group({
name: '',
@ -59,7 +60,7 @@ export class SchemaEditFormComponent implements OnInit {
}
public saveSchema() {
this.editForm.markAsTouched();
this.editFormSubmitted = true;
if (this.editForm.valid) {
this.editForm.disable();
@ -78,6 +79,7 @@ export class SchemaEditFormComponent implements OnInit {
}
public reset() {
this.editFormSubmitted = false;
this.editForm.reset();
}

2
src/Squidex/app/features/schemas/pages/schema/schema-page.component.html

@ -45,7 +45,7 @@
</div>
<div class="form-group">
<sqx-control-errors for="name" [submitted]="addFieldForm.touched"></sqx-control-errors>
<sqx-control-errors for="name" [submitted]="addFieldFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter field name" />
</div>

5
src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts

@ -54,6 +54,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public isPublished: boolean;
public addFieldFormSubmitted = false;
public addFieldForm: FormGroup =
this.formBuilder.group({
type: ['string',
@ -171,7 +172,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
}
public addField() {
this.addFieldForm.markAsTouched();
this.addFieldFormSubmitted = true;
if (this.addFieldForm.valid) {
this.addFieldForm.disable();
@ -183,6 +184,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
const reset = () => {
this.addFieldForm.get('name').reset();
this.addFieldForm.enable();
this.addFieldFormSubmitted = false;
};
this.appName()
@ -205,6 +207,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
}
public resetFieldForm() {
this.addFieldFormSubmitted = false;
this.addFieldForm.reset();
}

2
src/Squidex/app/features/schemas/pages/schema/types/boolean-ui.component.html

@ -3,7 +3,7 @@
<label for="field-placeholder" class="col-xs-3 col-form-label">Placeholder</label>
<div class="col-xs-6">
<sqx-control-errors for="placeholder" [submitted]="editForm.touched"></sqx-control-errors>
<sqx-control-errors for="placeholder" [submitted]="editFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="field-placeholder" maxlength="100" formControlName="placeholder" />

2
src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.html

@ -3,7 +3,7 @@
<label for="field-placeholder" class="col-xs-3 col-form-label">Placeholder</label>
<div class="col-xs-6">
<sqx-control-errors for="placeholder" [submitted]="editForm.touched"></sqx-control-errors>
<sqx-control-errors for="placeholder" [submitted]="editFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="field-placeholder" maxlength="100" formControlName="placeholder" />

2
src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.html

@ -3,7 +3,7 @@
<label for="field-input-placeholder" class="col-xs-3 col-form-label">Placeholder</label>
<div class="col-xs-6">
<sqx-control-errors for="placeholder" [submitted]="editForm.touched"></sqx-control-errors>
<sqx-control-errors for="placeholder" [submitted]="editFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="field-input-placeholder" maxlength="100" formControlName="placeholder" />

2
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html

@ -8,7 +8,7 @@
<div class="form-group">
<label for="schema-name">Name</label>
<sqx-control-errors for="name" [submitted]="createForm.touched"></sqx-control-errors>
<sqx-control-errors for="name" [submitted]="createFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="schema-name" formControlName="name" />

4
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts

@ -40,6 +40,7 @@ export class SchemaFormComponent {
public cancelled = new EventEmitter();
public creationError = '';
public createFormSubmitted = false;
public createForm: FormGroup =
this.formBuilder.group({
name: ['',
@ -62,7 +63,7 @@ export class SchemaFormComponent {
}
public createSchema() {
this.createForm.markAsTouched();
this.createFormSubmitted = true;
if (this.createForm.valid) {
this.createForm.disable();
@ -85,6 +86,7 @@ export class SchemaFormComponent {
public reset() {
this.creationError = '';
this.createForm.reset();
this.createFormSubmitted = false;
}
public cancel() {

2
src/Squidex/app/features/settings/pages/clients/client.component.html

@ -15,7 +15,7 @@
<div class="client-header">
<form *ngIf="isRenaming" class="form-inline" [formGroup]="renameForm" (ngSubmit)="rename()">
<div class="form-group">
<sqx-control-errors for="name" [submitted]="renameForm.touched"></sqx-control-errors>
<sqx-control-errors for="name" [submitted]="renameFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control client-name enabled" formControlName="name" maxlength="20" sqxFocusOnInit (keydown)="onKeyDown($event.keyCode)" />
</div>

2
src/Squidex/app/features/settings/pages/clients/clients-page.component.html

@ -22,7 +22,7 @@
<div class="table-items-footer">
<form class="form-inline" [formGroup]="addClientForm" (ngSubmit)="attachClient()">
<div class="form-group">
<sqx-control-errors for="name" [submitted]="addClientForm.touched"></sqx-control-errors>
<sqx-control-errors for="name" [submitted]="addClientFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter client name" />
</div>

92
src/Squidex/app/framework/angular/control-errors.component.ts

@ -5,35 +5,32 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { ChangeDetectorRef, ChangeDetectionStrategy, Component, Host, Input, OnChanges, OnInit, OnDestroy, Optional } from '@angular/core';
import { Component, Host, Input, OnChanges, OnInit, Optional } from '@angular/core';
import { AbstractControl, FormGroupDirective } from '@angular/forms';
import { Subscription } from 'rxjs';
import { fadeAnimation } from './animations';
const DEFAULT_ERRORS: { [key: string]: string } = {
required: '{field} is required.',
pattern: '{field} does not follow the pattern.',
patternMessage: '{message}',
minValue: '{field} must be larget than {minValue}.',
maxValue: '{field} must be larget than {maxValue}.',
minLength: '{field} must have more than {minLength} characters.',
maxLength: '{field} cannot have more than {maxLength} characters.',
validNumber: '{field} is not a valid number.',
validValues: '{field} is not a valid value.'
patternmessage: '{message}',
minvalue: '{field} must be larger than {minValue}.',
maxvalue: '{field} must be smaller than {maxValue}.',
minlength: '{field} must have more than {requiredLength} characters.',
maxlength: '{field} cannot have more than {requiredLength} characters.',
validnumber: '{field} is not a valid number.',
validvalues: '{field} is not a valid value.'
};
@Component({
selector: 'sqx-control-errors',
styleUrls: ['./control-errors.component.scss'],
templateUrl: './control-errors.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
fadeAnimation
]
})
export class ControlErrorsComponent implements OnChanges, OnInit, OnDestroy {
private formSubscription: Subscription;
export class ControlErrorsComponent implements OnChanges, OnInit {
private displayFieldName: string;
private control: AbstractControl;
@ -49,51 +46,22 @@ export class ControlErrorsComponent implements OnChanges, OnInit, OnDestroy {
@Input()
public submitted: boolean;
public errorMessages: string[];
constructor(@Optional() @Host() private readonly form: FormGroupDirective,
private readonly changeDetector: ChangeDetectorRef
) {
if (!this.form) {
throw new Error('control-errors must be used with a parent formGroup directive');
}
}
public ngOnChanges() {
if (this.fieldName) {
this.displayFieldName = this.fieldName;
} else if (this.for) {
this.displayFieldName = this.for.substr(0, 1).toUpperCase() + this.for.substr(1);
}
this.update();
}
public ngOnDestroy() {
this.formSubscription.unsubscribe();
}
public ngOnInit() {
this.control = this.form.form.controls[this.for];
this.formSubscription =
this.form.form.statusChanges.merge(this.control.statusChanges)
.subscribe(_ => {
this.update();
});
}
private update() {
public get errorMessages(): string[] {
if (!this.control) {
return;
return null;
}
if (this.control.invalid && (this.control.touched || this.form.form.touched)) {
if (this.control.invalid && (this.control.touched || this.submitted)) {
const errors: string[] = [];
for (let key in <any>this.control.errors) {
if (this.control.errors.hasOwnProperty(key)) {
let message: string = (this.errors ? this.errors[key] : null) || DEFAULT_ERRORS[key];
if (!message) {
continue;
}
let properties = this.control.errors[key];
for (let property in properties) {
@ -108,11 +76,29 @@ export class ControlErrorsComponent implements OnChanges, OnInit, OnDestroy {
}
}
this.errorMessages = errors.length > 0 ? errors : null;
} else {
this.errorMessages = null;
return errors.length > 0 ? errors : null;
}
return null;
}
constructor(
@Optional() @Host() private readonly formGroupDirective: FormGroupDirective
) {
if (!this.formGroupDirective) {
throw new Error('control-errors must be used with a parent formGroup directive');
}
}
public ngOnChanges() {
if (this.fieldName) {
this.displayFieldName = this.fieldName;
} else if (this.for) {
this.displayFieldName = this.for.substr(0, 1).toUpperCase() + this.for.substr(1);
}
}
this.changeDetector.markForCheck();
public ngOnInit() {
this.control = this.formGroupDirective.form.controls[this.for];
}
}

14
src/Squidex/app/framework/angular/validators.ts

@ -33,7 +33,7 @@ export class ValidatorsEx {
if (!regex.test(n)) {
if (message) {
return { patternMessage: { requiredPattern: regexStr, actualValue: n, message } };
return { patternmessage: { requiredPattern: regexStr, actualValue: n, message } };
} else {
return { pattern: { requiredPattern: regexStr, actualValue: n } };
}
@ -44,15 +44,19 @@ export class ValidatorsEx {
}
public static between(minValue: number | undefined, maxValue: number | undefined) {
if (!minValue || !maxValue) {
return Validators.nullValidator;
}
return (control: AbstractControl): { [key: string]: any } => {
const n: number = control.value;
if (typeof n !== 'number') {
return { validNumber: false };
return { validnumber: false };
} else if (minValue && n < minValue) {
return { minValue: { minValue, actualValue: n } };
return { minvalue: { minValue, actualValue: n } };
} else if (maxValue && n > maxValue) {
return { maxValue: { maxValue, actualValue: n } };
return { maxvalue: { maxValue, actualValue: n } };
}
return {};
@ -64,7 +68,7 @@ export class ValidatorsEx {
const n: T = control.value;
if (values.indexOf(n) < 0) {
return { validValues: false };
return { validvalues: false };
}
return {};

2
src/Squidex/app/shared/components/app-form.component.html

@ -8,7 +8,7 @@
<div class="form-group">
<label for="app-name">Name</label>
<sqx-control-errors for="name" [submitted]="createForm.touched"></sqx-control-errors>
<sqx-control-errors for="name" [submitted]="createFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="app-name" formControlName="name" />

6
src/Squidex/app/shared/components/app-form.component.ts

@ -29,6 +29,7 @@ export class AppFormComponent {
public cancelled = new EventEmitter();
public creationError = '';
public createFormSubmitted = false;
public createForm: FormGroup =
this.formBuilder.group({
name: ['',
@ -50,7 +51,7 @@ export class AppFormComponent {
}
public createApp() {
this.createForm.markAsTouched();
this.createFormSubmitted = true;
if (this.createForm.valid) {
this.createForm.disable();
@ -69,8 +70,9 @@ export class AppFormComponent {
}
private reset() {
this.createForm.enable();
this.creationError = '';
this.createForm.enable();
this.createFormSubmitted = false;
}
public cancel() {

8
tests/Squidex.Core.Tests/Schemas/NumberFieldPropertiesTests.cs

@ -79,7 +79,7 @@ namespace Squidex.Core.Schemas
}
[Fact]
public void Should_add_error_if_allowed_values_and_max_is_specified()
public void Should_add_error_if_allowed_values_and_max_value_is_specified()
{
var sut = new NumberFieldProperties { MaxValue = 10, AllowedValues = ImmutableList.Create<double>(4) };
@ -88,12 +88,12 @@ namespace Squidex.Core.Schemas
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Either or allowed values or range can be defined", "AllowedValues", "MinValue", "MaxValue")
new ValidationError("Either allowed values or min and max value can be defined", "AllowedValues", "MinValue", "MaxValue")
});
}
[Fact]
public void Should_add_error_if_allowed_values_and_min_is_specified()
public void Should_add_error_if_allowed_values_and_min_value_is_specified()
{
var sut = new NumberFieldProperties { MinValue = 10, AllowedValues = ImmutableList.Create<double>(4) };
@ -102,7 +102,7 @@ namespace Squidex.Core.Schemas
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Either or allowed values or range can be defined", "AllowedValues", "MinValue", "MaxValue")
new ValidationError("Either allowed values or min and max value can be defined", "AllowedValues", "MinValue", "MaxValue")
});
}

29
tests/Squidex.Core.Tests/Schemas/StringFieldPropertiesTests.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using FluentAssertions;
@ -34,6 +35,34 @@ namespace Squidex.Core.Schemas
});
}
[Fact]
public void Should_add_error_if_allowed_values_and_max_value_is_specified()
{
var sut = new StringFieldProperties { MinLength = 10, AllowedValues = ImmutableList.Create("4") };
sut.Validate(errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Either allowed values or min and max length can be defined", "AllowedValues", "MinLength", "MaxLength")
});
}
[Fact]
public void Should_add_error_if_allowed_values_and_min_value_is_specified()
{
var sut = new StringFieldProperties { MaxLength = 10, AllowedValues = ImmutableList.Create("4") };
sut.Validate(errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Either allowed values or min and max length can be defined", "AllowedValues", "MinLength", "MaxLength")
});
}
[Fact]
public void Should_add_error_if_radio_button_has_no_allowed_values()
{

168
tests/Squidex.Infrastructure.Tests/CQRS/Commands/DefaultDomainObjectRepositoryTests.cs

@ -0,0 +1,168 @@
// ==========================================================================
// DefaultDomainObjectRepositoryTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Moq;
using Squidex.Infrastructure.CQRS.Events;
using Xunit;
using System.Collections.Generic;
using System.Linq;
// ReSharper disable PrivateFieldCanBeConvertedToLocalVariable
namespace Squidex.Infrastructure.CQRS.Commands
{
public class DefaultDomainObjectRepositoryTests
{
private readonly Mock<IDomainObjectFactory> factory = new Mock<IDomainObjectFactory>();
private readonly Mock<IEventStore> eventStore = new Mock<IEventStore>();
private readonly Mock<IEventPublisher> eventPublisher = new Mock<IEventPublisher>();
private readonly Mock<IStreamNameResolver> streamNameResolver = new Mock<IStreamNameResolver>();
private readonly Mock<EventDataFormatter> eventDataFormatter = new Mock<EventDataFormatter>(null);
private readonly string streamName = Guid.NewGuid().ToString();
private readonly Guid aggregateId = Guid.NewGuid();
private readonly MyDomainObject domainObject;
private readonly DefaultDomainObjectRepository sut;
public DefaultDomainObjectRepositoryTests()
{
domainObject = new MyDomainObject(aggregateId, 123);
streamNameResolver.Setup(x => x.GetStreamName(It.IsAny<Type>(), aggregateId)).Returns(streamName);
factory.Setup(x => x.CreateNew(typeof(MyDomainObject), aggregateId)).Returns(domainObject);
sut = new DefaultDomainObjectRepository(factory.Object, eventStore.Object, eventPublisher.Object, streamNameResolver.Object, eventDataFormatter.Object);
}
public sealed class MyEvent : IEvent
{
}
public sealed class MyDomainObject : DomainObject
{
private readonly List<IEvent> appliedEvents = new List<IEvent>();
public List<IEvent> AppliedEvents
{
get { return appliedEvents; }
}
public MyDomainObject(Guid id, int version) : base(id, version)
{
}
public void AddEvent(IEvent @event)
{
RaiseEvent(@event);
}
protected override void DispatchEvent(Envelope<IEvent> @event)
{
appliedEvents.Add(@event.Payload);
}
}
[Fact]
public async Task Should_throw_exception_when_event_store_returns_no_events()
{
eventStore.Setup(x => x.GetEventsAsync(streamName)).Returns(Observable.Empty<EventData>());
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetByIdAsync<MyDomainObject>(aggregateId));
}
[Fact]
public async Task Should_apply_domain_objects_to_event()
{
var eventData1 = new EventData();
var eventData2 = new EventData();
var event1 = new MyEvent();
var event2 = new MyEvent();
eventStore.Setup(x => x.GetEventsAsync(streamName)).Returns(new[] { eventData1, eventData2 }.ToObservable());
eventDataFormatter.Setup(x => x.Parse(eventData1)).Returns(new Envelope<IEvent>(event1));
eventDataFormatter.Setup(x => x.Parse(eventData2)).Returns(new Envelope<IEvent>(event2));
var result = await sut.GetByIdAsync<MyDomainObject>(aggregateId);
Assert.Equal(result.AppliedEvents, new[] { event1, event2 });
}
[Fact]
public async Task Should_throw_exception_if_final_version_does_not_match_to_expected()
{
var eventData1 = new EventData();
var eventData2 = new EventData();
var event1 = new MyEvent();
var event2 = new MyEvent();
eventStore.Setup(x => x.GetEventsAsync(streamName)).Returns(new[] { eventData1, eventData2 }.ToObservable());
eventDataFormatter.Setup(x => x.Parse(eventData1)).Returns(new Envelope<IEvent>(event1));
eventDataFormatter.Setup(x => x.Parse(eventData2)).Returns(new Envelope<IEvent>(event2));
await Assert.ThrowsAsync<DomainObjectVersionException>(() => sut.GetByIdAsync<MyDomainObject>(aggregateId, 200));
}
[Fact]
public async Task Should_append_events_and_publish()
{
var commitId = Guid.NewGuid();
var event1 = new MyEvent();
var event2 = new MyEvent();
var eventData1 = new EventData();
var eventData2 = new EventData();
eventDataFormatter.Setup(x => x.ToEventData(It.Is<Envelope<IEvent>>(e => e.Payload == event1), commitId)).Returns(eventData1);
eventDataFormatter.Setup(x => x.ToEventData(It.Is<Envelope<IEvent>>(e => e.Payload == event2), commitId)).Returns(eventData2);
eventStore.Setup(x => x.AppendEventsAsync(commitId, streamName, 122, It.Is<IEnumerable<EventData>>(e => e.Count() == 2))).Returns(Task.FromResult(true)).Verifiable();
domainObject.AddEvent(event1);
domainObject.AddEvent(event2);
await sut.SaveAsync(domainObject, domainObject.GetUncomittedEvents(), commitId);
eventPublisher.Verify(x => x.Publish(eventData1));
eventPublisher.Verify(x => x.Publish(eventData2));
eventStore.VerifyAll();
}
[Fact]
public async Task Should_throw_exception_on_version_mismatch()
{
var commitId = Guid.NewGuid();
var event1 = new MyEvent();
var event2 = new MyEvent();
var eventData1 = new EventData();
var eventData2 = new EventData();
eventDataFormatter.Setup(x => x.ToEventData(It.Is<Envelope<IEvent>>(e => e.Payload == event1), commitId)).Returns(eventData1);
eventDataFormatter.Setup(x => x.ToEventData(It.Is<Envelope<IEvent>>(e => e.Payload == event2), commitId)).Returns(eventData2);
eventStore.Setup(x => x.AppendEventsAsync(commitId, streamName, 122, new List<EventData> { eventData1, eventData2 })).Throws(new WrongEventVersionException(1, 2)).Verifiable();
domainObject.AddEvent(event1);
domainObject.AddEvent(event2);
await Assert.ThrowsAsync<DomainObjectVersionException>(() => sut.SaveAsync(domainObject, domainObject.GetUncomittedEvents(), commitId));
eventStore.VerifyAll();
}
}
}
Loading…
Cancel
Save