Browse Source

Components (#710)

* Components

* Code improvements and more tests.

* Fix test (but why?)
pull/712/head
Sebastian Stehle 5 years ago
committed by GitHub
parent
commit
7e7c324372
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Component.cs
  2. 5
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs
  3. 10
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs
  4. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs
  5. 12
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs
  6. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs
  7. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs
  8. 39
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs
  9. 70
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs
  10. 24
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentInterfaceGraphType.cs
  11. 60
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentUnionGraphType.cs
  12. 15
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs
  13. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs
  14. 27
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataFlatGraphType.cs
  15. 59
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs
  16. 80
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs
  17. 27
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedGraphType.cs
  18. 37
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ReferenceUnionGraphType.cs
  19. 90
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs
  20. 17
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs
  21. 8
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedTypes.cs
  22. 1
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs
  23. 212
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs
  24. 35
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs
  25. 164
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs
  26. 110
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  27. 25
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestApp.cs
  28. 12
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestAsset.cs
  29. 493
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs
  30. 76
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs
  31. 1884
      frontend/package-lock.json
  32. 94
      frontend/package.json

25
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Component.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Diagnostics.CodeAnalysis;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
@ -15,5 +16,29 @@ namespace Squidex.Domain.Apps.Core.Contents
public sealed record Component(string Type, JsonObject Data, Schema Schema) public sealed record Component(string Type, JsonObject Data, Schema Schema)
{ {
public const string Discriminator = "schemaId"; public const string Discriminator = "schemaId";
public static bool IsValid(IJsonValue? value, [MaybeNullWhen(false)] out string discriminator)
{
discriminator = null!;
if (value is not JsonObject obj)
{
return false;
}
if (!obj.TryGetValue<JsonString>(Discriminator, out var type))
{
return false;
}
if (string.IsNullOrWhiteSpace(type.Value))
{
return false;
}
discriminator = type.Value;
return true;
}
} }
} }

5
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs

@ -73,6 +73,11 @@ namespace Squidex.Domain.Apps.Core.Schemas
return (withHidden || !field.IsHidden) && !field.RawProperties.IsUIProperty(); return (withHidden || !field.IsHidden) && !field.RawProperties.IsUIProperty();
} }
public static bool IsComponentLike<T>(this T field) where T : IField
{
return field.RawProperties is ComponentFieldProperties || field.RawProperties is ComponentsFieldProperties;
}
public static bool IsUI<T>(this T field) where T : IField public static bool IsUI<T>(this T field) where T : IField
{ {
return field.RawProperties is UIFieldProperties; return field.RawProperties is UIFieldProperties;

10
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs

@ -45,11 +45,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
return field.RawProperties.Label.Or(field.TypeName()); return field.RawProperties.Label.Or(field.TypeName());
} }
public static bool IsSingleton(this Schema schema)
{
return schema.Type == SchemaType.Singleton;
}
public static string TypeName(this Schema schema) public static string TypeName(this Schema schema)
{ {
return schema.Name.ToPascalCase(); return schema.Name.ToPascalCase();
@ -65,11 +60,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
return schema.Properties.Label.Or(schema.Name); return schema.Properties.Label.Or(schema.Name);
} }
public static DomainId SingleId(this ReferencesFieldProperties properties)
{
return properties.SchemaIds?.Count == 1 ? properties.SchemaIds[0] : default;
}
public static IEnumerable<RootField> ReferenceFields(this Schema schema) public static IEnumerable<RootField> ReferenceFields(this Schema schema)
{ {
return schema.RootFields(schema.FieldsInReferences); return schema.RootFields(schema.FieldsInReferences);

2
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs

@ -199,7 +199,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (null, new JsonError(T.Get("contents.invalidArrayOfStrings"))); return (null, new JsonError(T.Get("contents.invalidArrayOfStrings")));
} }
private (object? Result, JsonError? Error) ConvertToComponentList(IField<ComponentsFieldProperties> field, IJsonValue value) private static (object? Result, JsonError? Error) ConvertToComponentList(IField<ComponentsFieldProperties> field, IJsonValue value)
{ {
if (value is JsonArray array) if (value is JsonArray array)
{ {

12
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs

@ -175,17 +175,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
private static bool IsValidComponent(IJsonValue value) private static bool IsValidComponent(IJsonValue value)
{ {
if (value is not JsonObject obj) return Component.IsValid(value, out _);
{
return false;
}
if (!obj.TryGetValue<JsonString>(Component.Discriminator, out var type))
{
return false;
}
return !string.IsNullOrWhiteSpace(type.Value);
} }
} }
} }

4
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs

@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return await model.ExecuteAsync(options); return await model.ExecuteAsync(options);
} }
private async Task<GraphQLModel> GetModelAsync(IAppEntity app) public async Task<GraphQLModel> GetModelAsync(IAppEntity app)
{ {
var entry = await GetModelEntryAsync(app); var entry = await GetModelEntryAsync(app);
@ -108,4 +108,4 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return $"GraphQLModel_{appId}_{etag}"; return $"GraphQLModel_{appId}_{etag}";
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs

@ -23,6 +23,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
private readonly GraphQLSchema schema; private readonly GraphQLSchema schema;
private readonly ISemanticLog log; private readonly ISemanticLog log;
public GraphQLSchema Schema => schema;
public GraphQLModel(IAppEntity app, IEnumerable<ISchemaEntity> schemas, SharedTypes typeFactory, ISemanticLog log) public GraphQLModel(IAppEntity app, IEnumerable<ISchemaEntity> schemas, SharedTypes typeFactory, ISemanticLog log)
{ {
this.log = log; this.log = log;

39
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs

@ -12,6 +12,7 @@ using GraphQL.Resolvers;
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
@ -22,11 +23,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
internal sealed class Builder internal sealed class Builder
{ {
private readonly Dictionary<SchemaInfo, ComponentGraphType> componentTypes = new Dictionary<SchemaInfo, ComponentGraphType>(ReferenceEqualityComparer.Instance);
private readonly Dictionary<SchemaInfo, ContentGraphType> contentTypes = new Dictionary<SchemaInfo, ContentGraphType>(ReferenceEqualityComparer.Instance); private readonly Dictionary<SchemaInfo, ContentGraphType> contentTypes = new Dictionary<SchemaInfo, ContentGraphType>(ReferenceEqualityComparer.Instance);
private readonly Dictionary<SchemaInfo, ContentResultGraphType> contentResultTypes = new Dictionary<SchemaInfo, ContentResultGraphType>(ReferenceEqualityComparer.Instance); private readonly Dictionary<SchemaInfo, ContentResultGraphType> contentResultTypes = new Dictionary<SchemaInfo, ContentResultGraphType>(ReferenceEqualityComparer.Instance);
private readonly FieldVisitor fieldVisitor; private readonly FieldVisitor fieldVisitor;
private readonly FieldInputVisitor fieldInputVisitor; private readonly FieldInputVisitor fieldInputVisitor;
private readonly PartitionResolver partitionResolver; private readonly PartitionResolver partitionResolver;
private readonly List<SchemaInfo> allSchemas = new List<SchemaInfo>();
public SharedTypes SharedTypes { get; } public SharedTypes SharedTypes { get; }
@ -48,7 +51,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
public GraphQLSchema BuildSchema(IEnumerable<ISchemaEntity> schemas) public GraphQLSchema BuildSchema(IEnumerable<ISchemaEntity> schemas)
{ {
var schemaInfos = SchemaInfo.Build(schemas).ToList(); // Do not add schema without fields.
allSchemas.AddRange(SchemaInfo.Build(schemas).Where(x => x.Fields.Count > 0));
// Only published normal schemas (not components are used for entities).
var schemaInfos = allSchemas.Where(x => x.Schema.SchemaDef.IsPublished && x.Schema.SchemaDef.Type != SchemaType.Component).ToList();
foreach (var schemaInfo in schemaInfos) foreach (var schemaInfo in schemaInfos)
{ {
@ -65,9 +72,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
newSchema.RegisterType(SharedTypes.ContentInterface); newSchema.RegisterType(SharedTypes.ContentInterface);
if (schemas.Any()) if (schemaInfos.Any())
{ {
newSchema.Mutation = new AppMutationsGraphType(this, schemaInfos); var mutations = new AppMutationsGraphType(this, schemaInfos);
if (mutations.Fields.Count > 0)
{
newSchema.Mutation = mutations;
}
} }
foreach (var (schemaInfo, contentType) in contentTypes) foreach (var (schemaInfo, contentType) in contentTypes)
@ -75,6 +87,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
contentType.Initialize(this, schemaInfo, schemaInfos); contentType.Initialize(this, schemaInfo, schemaInfos);
} }
foreach (var (schemaInfo, componentType) in componentTypes)
{
componentType.Initialize(this, schemaInfo);
}
foreach (var contentType in contentTypes.Values) foreach (var contentType in contentTypes.Values)
{ {
newSchema.RegisterType(contentType); newSchema.RegisterType(contentType);
@ -100,6 +117,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
return fieldInfo.Field.Accept(fieldVisitor, fieldInfo); return fieldInfo.Field.Accept(fieldVisitor, fieldInfo);
} }
public IObjectGraphType GetContentResultType(SchemaInfo schemaId)
{
return contentResultTypes.GetOrDefault(schemaId);
}
public IObjectGraphType? GetContentType(DomainId schemaId) public IObjectGraphType? GetContentType(DomainId schemaId)
{ {
return contentTypes.FirstOrDefault(x => x.Key.Schema.Id == schemaId).Value; return contentTypes.FirstOrDefault(x => x.Key.Schema.Id == schemaId).Value;
@ -110,9 +132,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
return contentTypes.GetOrDefault(schemaId); return contentTypes.GetOrDefault(schemaId);
} }
public IObjectGraphType GetContentResultType(SchemaInfo schemaId) public IObjectGraphType? GetComponentType(DomainId schemaId)
{ {
return contentResultTypes.GetOrDefault(schemaId); var schema = allSchemas.Find(x => x.Schema.Id == schemaId);
if (schema == null)
{
return null;
}
return componentTypes.GetOrAdd(schema, x => new ComponentGraphType(this, schema));
} }
public IEnumerable<KeyValuePair<SchemaInfo, ContentGraphType>> GetAllContentTypes() public IEnumerable<KeyValuePair<SchemaInfo, ContentGraphType>> GetAllContentTypes()

70
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs

@ -0,0 +1,70 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
internal sealed class ComponentGraphType : ObjectGraphType<JsonObject>
{
public ComponentGraphType(Builder builder, SchemaInfo schemaInfo)
{
Name = schemaInfo.ComponentType;
Description = $"The structure of the {schemaInfo.DisplayName} component schema.";
AddField(ContentFields.SchemaId);
AddResolvedInterface(builder.SharedTypes.ComponentInterface);
IsTypeOf = CheckType(schemaInfo.Schema.Id.ToString());
}
public void Initialize(Builder builder, SchemaInfo schemaInfo)
{
foreach (var fieldInfo in schemaInfo.Fields)
{
if (fieldInfo.Field.IsComponentLike())
{
AddField(new FieldType
{
Name = fieldInfo.FieldNameDynamic,
Arguments = ContentActions.Json.Arguments,
ResolvedType = AllTypes.Json,
Resolver = FieldVisitor.JsonPath,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(fieldInfo);
}
var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
if (resolvedType != null && resolver != null)
{
AddField(new FieldType
{
Name = fieldInfo.FieldName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(fieldInfo);
}
}
}
private static Func<object, bool> CheckType(string schemaId)
{
return value =>
{
return Component.IsValid(value as IJsonValue, out var discrimiator) && discrimiator == schemaId.ToString();
};
}
}
}

24
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentInterfaceGraphType.cs

@ -0,0 +1,24 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using GraphQL.Types;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
internal sealed class ComponentInterfaceGraphType : InterfaceGraphType<JsonObject>
{
public ComponentInterfaceGraphType()
{
Name = "Component";
AddField(ContentFields.SchemaId);
Description = "The structure of all content types.";
}
}
}

60
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentUnionGraphType.cs

@ -0,0 +1,60 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
internal sealed class ComponentUnionGraphType : UnionGraphType
{
private readonly Dictionary<string, IObjectGraphType> types = new Dictionary<string, IObjectGraphType>();
public bool HasType => types.Count > 0;
public ComponentUnionGraphType(Builder builder, FieldInfo fieldInfo, ImmutableList<DomainId>? schemaIds)
{
Name = fieldInfo.ReferenceType;
if (schemaIds?.Any() == true)
{
foreach (var schemaId in schemaIds)
{
var contentType = builder.GetComponentType(schemaId);
if (contentType != null)
{
types[schemaId.ToString()] = contentType;
}
}
}
if (HasType)
{
foreach (var type in types)
{
AddPossibleType(type.Value);
}
ResolveType = value =>
{
if (value is JsonObject component && component.TryGetValue<JsonString>(Component.Discriminator, out var schemaId))
{
return types.GetOrDefault(schemaId.Value);
}
return null;
};
}
}
}
}

15
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs

@ -8,7 +8,9 @@
using System; using System;
using GraphQL.Resolvers; using GraphQL.Resolvers;
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
@ -94,6 +96,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Description = "The color status of the content." Description = "The color status of the content."
}; };
public static readonly FieldType SchemaId = new FieldType
{
Name = "schemaId",
ResolvedType = AllTypes.NonNullString,
Resolver = Resolve(x => x[Component.Discriminator].ToString()),
Description = "The id of the schema."
};
private static IFieldResolver Resolve<T>(Func<JsonObject, T> resolver)
{
return Resolvers.Sync(resolver);
}
private static IFieldResolver Resolve<T>(Func<IEnrichedContentEntity, T> resolver) private static IFieldResolver Resolve<T>(Func<IEnrichedContentEntity, T> resolver)
{ {
return Resolvers.Sync(resolver); return Resolvers.Sync(resolver);

4
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using GraphQL.Types; using GraphQL.Types;
@ -33,7 +34,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
AddField(ContentFields.LastModifiedByUser); AddField(ContentFields.LastModifiedByUser);
AddField(ContentFields.Status); AddField(ContentFields.Status);
AddField(ContentFields.StatusColor); AddField(ContentFields.StatusColor);
AddResolvedInterface(builder.SharedTypes.ContentInterface); AddResolvedInterface(builder.SharedTypes.ContentInterface);
Description = $"The structure of a {schemaInfo.DisplayName} content type."; Description = $"The structure of a {schemaInfo.DisplayName} content type.";
@ -43,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
private bool CheckType(object value) private bool CheckType(object value)
{ {
return value is IContentEntity content && content.SchemaId?.Id == schemaId; return value is IContentEntity content && content.SchemaId?.Id == schemaId;
} }
public void Initialize(Builder builder, SchemaInfo schemaInfo, IEnumerable<SchemaInfo> allSchemas) public void Initialize(Builder builder, SchemaInfo schemaInfo, IEnumerable<SchemaInfo> allSchemas)

27
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataFlatGraphType.cs

@ -19,8 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
foreach (var fieldInfo in schemaInfo.Fields) foreach (var fieldInfo in schemaInfo.Fields)
{ {
if (fieldInfo.Field.RawProperties is ComponentFieldProperties || if (fieldInfo.Field.IsComponentLike())
fieldInfo.Field.RawProperties is ComponentsFieldProperties)
{ {
AddField(new FieldType AddField(new FieldType
{ {
@ -31,21 +30,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Description = fieldInfo.Field.RawProperties.Hints Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(fieldInfo); }).WithSourceName(fieldInfo);
} }
else
{
var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
if (resolver != null) var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
if (resolver != null)
{
AddField(new FieldType
{ {
AddField(new FieldType Name = fieldInfo.FieldName,
{ Arguments = args,
Name = fieldInfo.FieldName, ResolvedType = resolvedType,
Arguments = args, Resolver = resolver,
ResolvedType = resolvedType, Description = fieldInfo.Field.RawProperties.Hints
Resolver = resolver, }).WithSourceName(fieldInfo);
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(fieldInfo);
}
} }
} }

59
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs

@ -20,16 +20,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
foreach (var fieldInfo in schemaInfo.Fields) foreach (var fieldInfo in schemaInfo.Fields)
{ {
if (fieldInfo.Field.RawProperties is ComponentFieldProperties || var partitioning = builder.ResolvePartition(((RootField)fieldInfo.Field).Partitioning);
fieldInfo.Field.RawProperties is ComponentsFieldProperties)
if (fieldInfo.Field.IsComponentLike())
{ {
var fieldGraphType = new ObjectGraphType var fieldGraphType = new ObjectGraphType
{ {
Name = fieldInfo.LocalizedType Name = fieldInfo.LocalizedType
}; };
var partitioning = builder.ResolvePartition(((RootField)fieldInfo.Field).Partitioning);
foreach (var partitionKey in partitioning.AllKeys) foreach (var partitionKey in partitioning.AllKeys)
{ {
fieldGraphType.AddField(new FieldType fieldGraphType.AddField(new FieldType
@ -51,40 +50,36 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Resolver = ContentResolvers.Field Resolver = ContentResolvers.Field
}).WithSourceName(fieldInfo); }).WithSourceName(fieldInfo);
} }
else
{
var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
if (resolver != null) var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
{
var fieldGraphType = new ObjectGraphType
{
Name = fieldInfo.LocalizedType
};
var partitioning = builder.ResolvePartition(((RootField)fieldInfo.Field).Partitioning); if (resolver != null)
{
var fieldGraphType = new ObjectGraphType
{
Name = fieldInfo.LocalizedType
};
foreach (var partitionKey in partitioning.AllKeys) foreach (var partitionKey in partitioning.AllKeys)
{
fieldGraphType.AddField(new FieldType
{ {
fieldGraphType.AddField(new FieldType Name = partitionKey.EscapePartition(),
{ Arguments = args,
Name = partitionKey.EscapePartition(), ResolvedType = resolvedType,
Arguments = args, Resolver = resolver,
ResolvedType = resolvedType, Description = fieldInfo.Field.RawProperties.Hints
Resolver = resolver, }).WithSourceName(partitionKey);
Description = fieldInfo.Field.RawProperties.Hints }
}).WithSourceName(partitionKey);
}
fieldGraphType.Description = $"The structure of the {fieldInfo.DisplayName} field of the {schemaInfo.DisplayName} content type."; fieldGraphType.Description = $"The structure of the {fieldInfo.DisplayName} field of the {schemaInfo.DisplayName} content type.";
AddField(new FieldType AddField(new FieldType
{ {
Name = fieldInfo.FieldName, Name = fieldInfo.FieldName,
ResolvedType = fieldGraphType, ResolvedType = fieldGraphType,
Resolver = ContentResolvers.Field Resolver = ContentResolvers.Field
}).WithSourceName(fieldInfo); }).WithSourceName(fieldInfo);
}
} }
} }

80
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs

@ -12,6 +12,8 @@ using GraphQL;
using GraphQL.Resolvers; using GraphQL.Resolvers;
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
@ -102,12 +104,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
return default; return default;
} }
var schemaFieldType = var type = new NestedGraphType(builder, args);
new ListGraphType(
new NonNullGraphType(
new NestedGraphType(builder, args)));
return (schemaFieldType, JsonNoop, null); if (type.Fields.Count == 0)
{
return default;
}
return (new ListGraphType(new NonNullGraphType(type)), JsonNoop, null);
} }
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<AssetsFieldProperties> field, FieldInfo args) public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<AssetsFieldProperties> field, FieldInfo args)
@ -122,12 +126,26 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<ComponentFieldProperties> field, FieldInfo args) public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<ComponentFieldProperties> field, FieldInfo args)
{ {
return (AllTypes.Json, JsonPath, ContentActions.Json.Arguments); var type = ResolveComponent(args, field.Properties.SchemaIds);
if (type == null)
{
return default;
}
return (type, JsonNoop, null);
} }
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<ComponentsFieldProperties> field, FieldInfo args) public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<ComponentsFieldProperties> field, FieldInfo args)
{ {
return (AllTypes.Json, JsonPath, ContentActions.Json.Arguments); var type = ResolveComponent(args, field.Properties.SchemaIds);
if (type == null)
{
return default;
}
return (new ListGraphType(new NonNullGraphType(type)), JsonNoop, null);
} }
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<DateTimeFieldProperties> field, FieldInfo args) public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<DateTimeFieldProperties> field, FieldInfo args)
@ -162,7 +180,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<ReferencesFieldProperties> field, FieldInfo args) public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<ReferencesFieldProperties> field, FieldInfo args)
{ {
return ResolveReferences(field, args); var type = ResolveReferences(args, field.Properties.SchemaIds);
if (type == null)
{
return default;
}
return (new ListGraphType(new NonNullGraphType(type)), References, null);
} }
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<UIFieldProperties> field, FieldInfo args) public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<UIFieldProperties> field, FieldInfo args)
@ -170,15 +195,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
return default; return default;
} }
private (IGraphType?, IFieldResolver?, QueryArguments?) ResolveReferences(IField<ReferencesFieldProperties> field, FieldInfo args) private IGraphType? ResolveReferences(FieldInfo fieldInfo, ImmutableList<DomainId>? schemaIds)
{ {
IGraphType? contentType = builder.GetContentType(field.Properties.SingleId()); IGraphType? contentType = null;
if (schemaIds?.Count == 1)
{
contentType = builder.GetContentType(schemaIds[0]);
}
if (contentType == null) if (contentType == null)
{ {
var union = new ContentUnionGraphType(builder, args, field.Properties); var union = new ReferenceUnionGraphType(builder, fieldInfo, schemaIds);
if (!union.PossibleTypes.Any()) if (!union.HasType)
{ {
return default; return default;
} }
@ -186,9 +216,31 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
contentType = union; contentType = union;
} }
var schemaFieldType = new ListGraphType(new NonNullGraphType(contentType)); return contentType;
}
private IGraphType? ResolveComponent(FieldInfo fieldInfo, ImmutableList<DomainId>? schemaIds)
{
IGraphType? componentType = null;
if (schemaIds?.Count == 1)
{
componentType = builder.GetComponentType(schemaIds[0]);
}
if (componentType == null)
{
var union = new ComponentUnionGraphType(builder, fieldInfo, schemaIds);
if (!union.HasType)
{
return default;
}
componentType = union;
}
return (schemaFieldType, References, null); return componentType;
} }
private static IFieldResolver CreateValueResolver(ValueResolver valueResolver) private static IFieldResolver CreateValueResolver(ValueResolver valueResolver)

27
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedGraphType.cs

@ -19,8 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
foreach (var nestedFieldInfo in fieldInfo.Fields) foreach (var nestedFieldInfo in fieldInfo.Fields)
{ {
if (nestedFieldInfo.Field.RawProperties is ComponentFieldProperties || if (nestedFieldInfo.Field.IsComponentLike())
nestedFieldInfo.Field.RawProperties is ComponentsFieldProperties)
{ {
AddField(new FieldType AddField(new FieldType
{ {
@ -31,21 +30,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Description = nestedFieldInfo.Field.RawProperties.Hints Description = nestedFieldInfo.Field.RawProperties.Hints
}).WithSourceName(nestedFieldInfo); }).WithSourceName(nestedFieldInfo);
} }
else
{
var (resolvedType, resolver, args) = builder.GetGraphType(nestedFieldInfo);
if (resolvedType != null && resolver != null) var (resolvedType, resolver, args) = builder.GetGraphType(nestedFieldInfo);
if (resolvedType != null && resolver != null)
{
AddField(new FieldType
{ {
AddField(new FieldType Name = nestedFieldInfo.FieldName,
{ Arguments = args,
Name = nestedFieldInfo.FieldName, ResolvedType = resolvedType,
Arguments = args, Resolver = resolver,
ResolvedType = resolvedType, Description = nestedFieldInfo.Field.RawProperties.Hints
Resolver = resolver, }).WithSourceName(nestedFieldInfo);
Description = nestedFieldInfo.Field.RawProperties.Hints
}).WithSourceName(nestedFieldInfo);
}
} }
} }

37
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentUnionGraphType.cs → backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ReferenceUnionGraphType.cs

@ -8,22 +8,24 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
internal sealed class ContentUnionGraphType : UnionGraphType internal sealed class ReferenceUnionGraphType : UnionGraphType
{ {
private readonly Dictionary<DomainId, IObjectGraphType> types = new Dictionary<DomainId, IObjectGraphType>(); private readonly Dictionary<DomainId, IObjectGraphType> types = new Dictionary<DomainId, IObjectGraphType>();
public ContentUnionGraphType(Builder builder, FieldInfo fieldInfo, ReferencesFieldProperties properties) public bool HasType => types.Count > 0;
public ReferenceUnionGraphType(Builder builder, FieldInfo fieldInfo, ImmutableList<DomainId>? schemaIds)
{ {
Name = fieldInfo.UnionType; Name = fieldInfo.ReferenceType;
if (properties.SchemaIds?.Any() == true) if (schemaIds?.Any() == true)
{ {
foreach (var schemaId in properties.SchemaIds) foreach (var schemaId in schemaIds)
{ {
var contentType = builder.GetContentType(schemaId); var contentType = builder.GetContentType(schemaId);
@ -41,20 +43,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
} }
} }
foreach (var type in types) if (HasType)
{ {
AddPossibleType(type.Value); foreach (var type in types)
}
ResolveType = value =>
{
if (value is IContentEntity content)
{ {
return types.GetOrDefault(content.SchemaId.Id); AddPossibleType(type.Value);
} }
return null; ResolveType = value =>
}; {
if (value is IContentEntity content)
{
return types.GetOrDefault(content.SchemaId.Id);
}
return null;
};
}
} }
} }
} }

90
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs

@ -23,6 +23,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
public string DisplayName { get; } public string DisplayName { get; }
public string ComponentType { get; }
public string ContentType { get; } public string ContentType { get; }
public string DataType { get; } public string DataType { get; }
@ -38,6 +40,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
private SchemaInfo(ISchemaEntity schema, string typeName, IReadOnlyList<FieldInfo> fields, Names names) private SchemaInfo(ISchemaEntity schema, string typeName, IReadOnlyList<FieldInfo> fields, Names names)
{ {
Schema = schema; Schema = schema;
ComponentType = names[$"{typeName}Component"];
ContentType = names[typeName]; ContentType = names[typeName];
DataFlatType = names[$"{typeName}FlatDataDto"]; DataFlatType = names[$"{typeName}FlatDataDto"];
DataInputType = names[$"{typeName}DataInputDto"]; DataInputType = names[$"{typeName}DataInputDto"];
@ -57,37 +60,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
var names = new Names(); var names = new Names();
var validSchemas = schemas.Where(x => foreach (var schema in schemas.OrderBy(x => x.Created))
x.SchemaDef.IsPublished &&
x.SchemaDef.Type != SchemaType.Component &&
x.SchemaDef.Fields.Count > 0);
foreach (var schema in validSchemas.OrderBy(x => x.Created))
{ {
var typeName = schema.TypeName(); var typeName = schema.TypeName();
var fields = FieldInfo.EmptyFields; var fieldInfos = new List<FieldInfo>(schema.SchemaDef.Fields.Count);
var fieldNames = new Names();
if (schema.SchemaDef.Fields.Count > 0) foreach (var field in schema.SchemaDef.Fields.ForApi())
{ {
var fieldNames = new Names(); fieldInfos.Add(FieldInfo.Build(
field,
fields = new List<FieldInfo>(schema.SchemaDef.Fields.Count); names[$"{typeName}Data{field.TypeName()}"],
names,
foreach (var field in schema.SchemaDef.Fields) fieldNames));
{
var fieldName = fieldNames[field];
fields.Add(FieldInfo.Build(
field,
fieldName,
fieldNames[fieldName.AsDynamic()],
names[$"{typeName}Data{field.TypeName()}"],
names));
}
} }
yield return new SchemaInfo(schema, typeName, fields, names); yield return new SchemaInfo(schema, typeName, fieldInfos, names);
} }
} }
} }
@ -112,22 +101,27 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
public string NestedInputType { get; } public string NestedInputType { get; }
public string UnionType { get; } public string ComponentType { get; }
public string ReferenceType { get; }
public IReadOnlyList<FieldInfo> Fields { get; } public IReadOnlyList<FieldInfo> Fields { get; }
private FieldInfo(IField field, string fieldName, string fieldNameDynamic, string typeName, IReadOnlyList<FieldInfo> fields, Names names) private FieldInfo(IField field, string typeName, Names names, Names parentNames, IReadOnlyList<FieldInfo> fields)
{ {
var fieldName = parentNames[field.Name.ToCamelCase(), false];
ComponentType = names[$"{typeName}ComponentUnionDto"];
DisplayName = field.DisplayName(); DisplayName = field.DisplayName();
Field = field; Field = field;
Fields = fields; Fields = fields;
FieldName = fieldName; FieldName = fieldName;
FieldNameDynamic = fieldNameDynamic; FieldNameDynamic = names[$"{fieldName}__Dynamic"];
LocalizedType = names[$"{typeName}Dto"]; LocalizedType = names[$"{typeName}Dto"];
LocalizedInputType = names[$"{typeName}InputDto"]; LocalizedInputType = names[$"{typeName}InputDto"];
NestedInputType = names[$"{typeName}ChildInputDto"]; NestedInputType = names[$"{typeName}ChildInputDto"];
NestedType = names[$"{typeName}ChildDto"]; NestedType = names[$"{typeName}ChildDto"];
UnionType = names[$"{typeName}UnionDto"]; ReferenceType = names[$"{typeName}UnionDto"];
} }
public override string ToString() public override string ToString()
@ -135,30 +129,28 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
return FieldName; return FieldName;
} }
internal static FieldInfo Build(IRootField rootField, string fieldName, string fieldNameDynamic, string typeName, Names names) internal static FieldInfo Build(IRootField rootField, string typeName, Names names, Names parentNames)
{ {
var fields = EmptyFields; var fieldInfos = EmptyFields;
if (rootField is IArrayField arrayField && arrayField.Fields.Count > 0) if (rootField is IArrayField arrayField)
{ {
var nestedNames = new Names(); var fieldNames = new Names();
fields = new List<FieldInfo>(arrayField.Fields.Count); fieldInfos = new List<FieldInfo>(arrayField.Fields.Count);
foreach (var field in arrayField.Fields) foreach (var nestedField in arrayField.Fields.ForApi())
{ {
var nestedName = nestedNames[field]; fieldInfos.Add(new FieldInfo(
nestedField,
fields.Add(new FieldInfo(field, names[$"{typeName}{nestedField.TypeName()}"],
nestedName, names,
nestedNames[nestedName.AsDynamic()], fieldNames,
$"{typeName}{field.TypeName()}", EmptyFields));
EmptyFields,
names));
} }
} }
return new FieldInfo(rootField, fieldName, fieldNameDynamic, typeName, fields, names); return new FieldInfo(rootField, typeName, names, parentNames, fieldInfos);
} }
} }
@ -170,6 +162,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
"Asset", "Asset",
"AssetResultDto", "AssetResultDto",
"Content", "Content",
"Component",
"EntityCreatedResultDto", "EntityCreatedResultDto",
"EntitySavedResultDto", "EntitySavedResultDto",
"JsonScalar", "JsonScalar",
@ -178,14 +171,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
}; };
private readonly Dictionary<string, int> takenNames = new Dictionary<string, int>(); private readonly Dictionary<string, int> takenNames = new Dictionary<string, int>();
public string this[IField field] public string this[string name, bool isEntity = true]
{
get => GetName(field.Name.ToCamelCase(), false);
}
public string this[string name]
{ {
get => GetName(name, true); get => GetName(name, isEntity);
} }
private string GetName(string name, bool isEntity) private string GetName(string name, bool isEntity)
@ -196,7 +184,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
name = "gql_" + name; name = "gql_" + name;
} }
else if (ReservedNames.Contains(name) && isEntity) else if (isEntity && ReservedNames.Contains(name))
{ {
name = $"{name}Entity"; name = $"{name}Entity";
} }

17
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs

@ -11,26 +11,11 @@ using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.ObjectPool; using Squidex.Infrastructure.ObjectPool;
using Squidex.Text;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
public static class Extensions public static class Extensions
{ {
public static string AsDynamic(this string value, bool input = false)
{
#pragma warning disable RECS0015 // If an extension method is called as static method convert it to method syntax
var result = CasingExtensions.ToCamelCase(value);
#pragma warning restore RECS0015 // If an extension method is called as static method convert it to method syntax
if (!input)
{
result = $"{result}__Dynamic";
}
return result;
}
internal static string BuildODataQuery(this IResolveFieldContext context) internal static string BuildODataQuery(this IResolveFieldContext context)
{ {
var sb = DefaultPools.StringBuilder.Get(); var sb = DefaultPools.StringBuilder.Get();
@ -111,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
return type.ResolvedType.Flatten(); return type.ResolvedType.Flatten();
} }
internal static IGraphType Flatten(this IGraphType type) public static IGraphType Flatten(this IGraphType type)
{ {
if (type is IProvideResolvedType provider) if (type is IProvideResolvedType provider)
{ {

8
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedTypes.cs

@ -19,6 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
private readonly Lazy<IGraphType> assetsList; private readonly Lazy<IGraphType> assetsList;
private readonly Lazy<IGraphType> assetsResult; private readonly Lazy<IGraphType> assetsResult;
private readonly Lazy<IInterfaceGraphType> contentInterface; private readonly Lazy<IInterfaceGraphType> contentInterface;
private readonly Lazy<IInterfaceGraphType> componentInterface;
private readonly Lazy<FieldType> findAsset; private readonly Lazy<FieldType> findAsset;
private readonly Lazy<FieldType> queryAssets; private readonly Lazy<FieldType> queryAssets;
private readonly Lazy<FieldType> queryAssetsWithTotal; private readonly Lazy<FieldType> queryAssetsWithTotal;
@ -31,6 +32,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
public IInterfaceGraphType ContentInterface => contentInterface.Value; public IInterfaceGraphType ContentInterface => contentInterface.Value;
public IInterfaceGraphType ComponentInterface => componentInterface.Value;
public FieldType FindAsset => findAsset.Value; public FieldType FindAsset => findAsset.Value;
public FieldType QueryAssets => queryAssets.Value; public FieldType QueryAssets => queryAssets.Value;
@ -59,6 +62,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
return new ContentInterfaceGraphType(); return new ContentInterfaceGraphType();
}); });
componentInterface = new Lazy<IInterfaceGraphType>(() =>
{
return new ComponentInterfaceGraphType();
});
findAsset = new Lazy<FieldType>(() => findAsset = new Lazy<FieldType>(() =>
{ {
return new FieldType return new FieldType

1
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs

@ -7,7 +7,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Squidex.Areas.Api.Controllers.Schemas.Models.Converters; using Squidex.Areas.Api.Controllers.Schemas.Models.Converters;
using Squidex.Areas.Api.Controllers.Schemas.Models.Fields;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;

212
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs

@ -5,9 +5,17 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using GraphQL; using GraphQL;
using GraphQL.Types;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Xunit; using Xunit;
using Schema = Squidex.Domain.Apps.Core.Schemas.Schema;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
@ -19,9 +27,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
const string query = @" const string query = @"
query IntrospectionQuery { query IntrospectionQuery {
__schema { __schema {
queryType { name } queryType {
mutationType { name } name
subscriptionType { name } }
mutationType {
name
}
subscriptionType {
name
}
types { types {
...FullType ...FullType
} }
@ -74,7 +88,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
fragment InputValue on __InputValue { fragment InputValue on __InputValue {
name name
description description
type { ...TypeRef } type {
...TypeRef
}
defaultValue defaultValue
} }
@ -101,5 +117,193 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
Assert.NotEmpty(json); Assert.NotEmpty(json);
} }
[Fact]
public async Task Should_create_empty_schema()
{
var model = await CreateSut().GetModelAsync(TestApp.Default);
Assert.NotNull(model);
}
[Fact]
public async Task Should_create_empty_schema_with_empty_schema()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "content"),
new Schema("content").Publish());
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
Assert.NotNull(model);
}
[Fact]
public async Task Should_create_empty_schema_with_empty_schema_because_ui_field()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "content"),
new Schema("content").Publish()
.AddUI(1, "ui", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
Assert.NotNull(model);
}
[Fact]
public async Task Should_create_empty_schema_with_empty_schema_because_invalid_field()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "content"),
new Schema("content").Publish()
.AddComponent(1, "component", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
Assert.NotNull(model);
}
[Fact]
public async Task Should_create_empty_schema_with_unpublished_schema()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "content"),
new Schema("content")
.AddString(1, "myField", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
Assert.NotNull(model);
}
[Fact]
public async Task Should_create_schema_with_reserved_schema_name()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "content"),
new Schema("content").Publish()
.AddString(1, "myField", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
Assert.Contains(model.Schema.AllTypes, x => x.Name == "ContentEntity");
}
[Fact]
public async Task Should_create_schema_with_reserved_field_name()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "my-schema"),
new Schema("my-schema").Publish()
.AddString(1, "content", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
var type = FindDataType(model, "MySchema");
Assert.Contains(type.Fields, x => x.Name == "content");
}
[Fact]
public async Task Should_create_schema_with_invalid_field_name()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "my-schema"),
new Schema("my-schema").Publish()
.AddString(1, "2-field", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
var type = FindDataType(model, "MySchema");
Assert.Contains(type.Fields, x => x.Name == "gql_2Field");
}
[Fact]
public async Task Should_create_schema_with_duplicate_field_names()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "my-schema"),
new Schema("my-schema").Publish()
.AddString(1, "my-field", Partitioning.Invariant)
.AddString(2, "my_field", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
var type = FindDataType(model, "MySchema");
Assert.Contains(type.Fields, x => x.Name == "myField");
Assert.Contains(type.Fields, x => x.Name == "myField2");
}
[Fact]
public async Task Should_not_create_schema_With_invalid_component()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "my-schema"),
new Schema("my-schema").Publish()
.AddComponent(1, "my-component", Partitioning.Invariant,
new ComponentFieldProperties())
.AddString(2, "my-string", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
var type = FindDataType(model, "MySchema");
Assert.DoesNotContain(type.Fields, x => x.Name == "myComponent");
}
[Fact]
public async Task Should_not_create_schema_With_invalid_components()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "my-schema"),
new Schema("my-schema").Publish()
.AddComponents(1, "my-components", Partitioning.Invariant,
new ComponentsFieldProperties())
.AddString(2, "my-string", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
var type = FindDataType(model, "MySchema");
Assert.DoesNotContain(type.Fields, x => x.Name == "myComponents");
}
[Fact]
public async Task Should_not_create_schema_With_invalid_references()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "my-schema"),
new Schema("my-schema").Publish()
.AddReferences(1, "my-references", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = DomainId.NewGuid() })
.AddString(2, "my-string", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
var type = FindDataType(model, "MySchema");
Assert.DoesNotContain(type.Fields, x => x.Name == "myReferences");
}
private static IObjectGraphType FindDataType(GraphQLModel model, string schema)
{
var type = (IObjectGraphType)model.Schema.AllTypes.Single(x => x.Name == schema);
return (IObjectGraphType)type.GetField("flatData").ResolvedType.Flatten();
}
} }
} }

35
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs

@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public GraphQLMutationTests() public GraphQLMutationTests()
{ {
content = TestContent.Create(appId, schemaId, contentId, schemaRefId1.Id, schemaRefId2.Id, null); content = TestContent.Create(contentId, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id, null);
A.CallTo(() => commandBus.PublishAsync(A<ICommand>.Ignored)) A.CallTo(() => commandBus.PublishAsync(A<ICommand>.Ignored))
.Returns(commandContext); .Returns(commandContext);
@ -105,7 +105,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<CreateContent>.That.Matches(x => A<CreateContent>.That.Matches(x =>
x.ExpectedVersion == EtagVersion.Any && x.ExpectedVersion == EtagVersion.Any &&
x.SchemaId.Equals(schemaId) && x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published && x.Status == Status.Published &&
x.Data.Equals(content.Data)))) x.Data.Equals(content.Data))))
.MustHaveHappened(); .MustHaveHappened();
@ -139,7 +139,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<CreateContent>.That.Matches(x => A<CreateContent>.That.Matches(x =>
x.ExpectedVersion == EtagVersion.Any && x.ExpectedVersion == EtagVersion.Any &&
x.ContentId == DomainId.Create("123") && x.ContentId == DomainId.Create("123") &&
x.SchemaId.Equals(schemaId) && x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published && x.Status == Status.Published &&
x.Data.Equals(content.Data)))) x.Data.Equals(content.Data))))
.MustHaveHappened(); .MustHaveHappened();
@ -172,7 +172,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<CreateContent>.That.Matches(x => A<CreateContent>.That.Matches(x =>
x.ExpectedVersion == EtagVersion.Any && x.ExpectedVersion == EtagVersion.Any &&
x.SchemaId.Equals(schemaId) && x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published && x.Status == Status.Published &&
x.Data.Equals(content.Data)))) x.Data.Equals(content.Data))))
.MustHaveHappened(); .MustHaveHappened();
@ -248,7 +248,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<UpdateContent>.That.Matches(x => A<UpdateContent>.That.Matches(x =>
x.ContentId == content.Id && x.ContentId == content.Id &&
x.ExpectedVersion == 10 && x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) && x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Data.Equals(content.Data)))) x.Data.Equals(content.Data))))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -281,7 +281,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<UpdateContent>.That.Matches(x => A<UpdateContent>.That.Matches(x =>
x.ContentId == content.Id && x.ContentId == content.Id &&
x.ExpectedVersion == 10 && x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) && x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Data.Equals(content.Data)))) x.Data.Equals(content.Data))))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -356,7 +356,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<UpsertContent>.That.Matches(x => A<UpsertContent>.That.Matches(x =>
x.ContentId == content.Id && x.ContentId == content.Id &&
x.ExpectedVersion == 10 && x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) && x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published && x.Status == Status.Published &&
x.Data.Equals(content.Data)))) x.Data.Equals(content.Data))))
.MustHaveHappened(); .MustHaveHappened();
@ -390,7 +390,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<UpsertContent>.That.Matches(x => A<UpsertContent>.That.Matches(x =>
x.ContentId == content.Id && x.ContentId == content.Id &&
x.ExpectedVersion == 10 && x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) && x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published && x.Status == Status.Published &&
x.Data.Equals(content.Data)))) x.Data.Equals(content.Data))))
.MustHaveHappened(); .MustHaveHappened();
@ -466,7 +466,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<PatchContent>.That.Matches(x => A<PatchContent>.That.Matches(x =>
x.ContentId == content.Id && x.ContentId == content.Id &&
x.ExpectedVersion == 10 && x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) && x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Data.Equals(content.Data)))) x.Data.Equals(content.Data))))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -499,7 +499,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<PatchContent>.That.Matches(x => A<PatchContent>.That.Matches(x =>
x.ContentId == content.Id && x.ContentId == content.Id &&
x.ExpectedVersion == 10 && x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) && x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Data.Equals(content.Data)))) x.Data.Equals(content.Data))))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -577,7 +577,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
x.ContentId == contentId && x.ContentId == contentId &&
x.DueTime == dueTime && x.DueTime == dueTime &&
x.ExpectedVersion == 10 && x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) && x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published))) x.Status == Status.Published)))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -611,7 +611,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
x.ContentId == contentId && x.ContentId == contentId &&
x.DueTime == null && x.DueTime == null &&
x.ExpectedVersion == 10 && x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) && x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published))) x.Status == Status.Published)))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -645,7 +645,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
x.ContentId == contentId && x.ContentId == contentId &&
x.DueTime == null && x.DueTime == null &&
x.ExpectedVersion == 10 && x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) && x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published))) x.Status == Status.Published)))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -723,20 +723,21 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<DeleteContent>.That.Matches(x => A<DeleteContent>.That.Matches(x =>
x.ContentId == contentId && x.ContentId == contentId &&
x.ExpectedVersion == 10 && x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId)))) x.SchemaId.Equals(TestSchemas.DefaultId))))
.MustHaveHappened(); .MustHaveHappened();
} }
private string CreateQuery(string query) private string CreateQuery(string query)
{ {
query = query query = query
.Replace("'", "\"")
.Replace("<ID>", contentId.ToString()) .Replace("<ID>", contentId.ToString())
.Replace("'", "\"")
.Replace("`", "\"")
.Replace("<FIELDS>", TestContent.AllFields); .Replace("<FIELDS>", TestContent.AllFields);
if (query.Contains("<DATA>")) if (query.Contains("<DATA>"))
{ {
var data = TestContent.Data(content, true, schemaRefId1.Id, schemaRefId2.Id); var data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id);
var dataJson = JsonConvert.SerializeObject(data, Formatting.Indented); var dataJson = JsonConvert.SerializeObject(data, Formatting.Indented);
var dataString = Regex.Replace(dataJson, "\"([^\"]+)\":", x => x.Groups[1].Value + ":").Replace(".0", string.Empty); var dataString = Regex.Replace(dataJson, "\"([^\"]+)\":", x => x.Groups[1].Value + ":").Replace(".0", string.Empty);
@ -751,7 +752,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
var input = new var input = new
{ {
data = TestContent.Data(content, true, schemaRefId1.Id, schemaRefId2.Id) data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id)
}; };
return JObject.FromObject(input).ToInputs(); return JObject.FromObject(input).ToInputs();

164
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs

@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}"); }");
var asset = TestAsset.Create(appId, DomainId.NewGuid()); var asset = TestAsset.Create(DomainId.NewGuid());
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null,
A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query" && x.NoTotal == true), A<CancellationToken>._)) A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query" && x.NoTotal == true), A<CancellationToken>._))
@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
data = new data = new
{ {
queryAssets = new dynamic[] queryAssets = new[]
{ {
TestAsset.Response(asset) TestAsset.Response(asset)
} }
@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}"); }");
var asset = TestAsset.Create(appId, DomainId.NewGuid()); var asset = TestAsset.Create(DomainId.NewGuid());
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null,
A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query" && x.NoTotal == false), A<CancellationToken>._)) A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query" && x.NoTotal == false), A<CancellationToken>._))
@ -108,7 +108,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
queryAssetsWithTotal = new queryAssetsWithTotal = new
{ {
total = 10, total = 10,
items = new dynamic[] items = new[]
{ {
TestAsset.Response(asset) TestAsset.Response(asset)
} }
@ -151,7 +151,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_return_single_asset_if_finding_asset() public async Task Should_return_single_asset_if_finding_asset()
{ {
var assetId = DomainId.NewGuid(); var assetId = DomainId.NewGuid();
var asset = TestAsset.Create(appId, assetId); var asset = TestAsset.Create(assetId);
var query = CreateQuery(@" var query = CreateQuery(@"
query { query {
@ -182,36 +182,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var query = CreateQuery(@" var query = CreateQuery(@"
query { query {
queryMySchemaContents(top: 30, skip: 5) { queryMySchemaContents(top: 30, skip: 5) {
id <FIELDS_CONTENT_FLAT>
version
created
createdBy
lastModified
lastModifiedBy
status
statusColor
url
flatData {
myString
myNumber
myBoolean
myDatetime
myJsonValue: myJson(path: 'value')
myJson
myGeolocation
myTags
myLocalized
myArray {
nestedNumber
nestedBoolean
}
}
} }
}"); }");
var content = TestContent.Create(appId, schemaId, DomainId.NewGuid(), DomainId.Empty, DomainId.Empty); var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(), A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(),
A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5" && x.NoTotal == true), A<CancellationToken>._)) A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5" && x.NoTotal == true), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(0, content)); .Returns(ResultList.CreateFrom(0, content));
@ -221,56 +199,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
data = new data = new
{ {
queryMySchemaContents = new dynamic[] queryMySchemaContents = new[]
{ {
new TestContent.FlatResponse(content)
{
id = content.Id,
version = 1,
created = content.Created,
createdBy = "subject:user1",
lastModified = content.LastModified,
lastModifiedBy = "client:client1",
status = "DRAFT",
statusColor = "red",
url = $"contents/my-schema/{content.Id}",
flatData = new
{
myString = "value",
myNumber = 1.0,
myBoolean = true,
myDatetime = content.LastModified.ToString(),
myJsonValue = 1,
myJson = new
{
value = 1
},
myGeolocation = new
{
latitude = 10,
longitude = 20
},
myTags = new[]
{
"tag1",
"tag2"
},
myLocalized = "de-DE",
myArray = new[]
{
new
{
nestedNumber = 10.0,
nestedBoolean = true
},
new
{
nestedNumber = 20.0,
nestedBoolean = false
}
}
}
}
} }
} }
}; };
@ -288,9 +219,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}"); }");
var content = TestContent.Create(appId, schemaId, DomainId.NewGuid(), DomainId.Empty, DomainId.Empty); var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(), A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(),
A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5" && x.NoTotal == true), A<CancellationToken>._)) A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5" && x.NoTotal == true), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(0, content)); .Returns(ResultList.CreateFrom(0, content));
@ -300,7 +232,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
data = new data = new
{ {
queryMySchemaContents = new dynamic[] queryMySchemaContents = new[]
{ {
TestContent.Response(content) TestContent.Response(content)
} }
@ -323,9 +255,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}"); }");
var content = TestContent.Create(appId, schemaId, DomainId.NewGuid(), DomainId.Empty, DomainId.Empty); var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(), A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(),
A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5" && x.NoTotal == false), A<CancellationToken>._)) A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5" && x.NoTotal == false), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(10, content)); .Returns(ResultList.CreateFrom(10, content));
@ -338,7 +271,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
queryMySchemaContentsWithTotal = new queryMySchemaContentsWithTotal = new
{ {
total = 10, total = 10,
items = new dynamic[] items = new[]
{ {
TestContent.Response(content) TestContent.Response(content)
} }
@ -381,7 +314,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_return_single_content_if_finding_content() public async Task Should_return_single_content_if_finding_content()
{ {
var contentId = DomainId.NewGuid(); var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, DomainId.Empty, DomainId.Empty); var content = TestContent.Create(contentId);
var query = CreateQuery(@" var query = CreateQuery(@"
query { query {
@ -410,7 +343,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_return_single_content_if_finding_content_with_version() public async Task Should_return_single_content_if_finding_content_with_version()
{ {
var contentId = DomainId.NewGuid(); var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, DomainId.Empty, DomainId.Empty); var content = TestContent.Create(contentId);
var query = CreateQuery(@" var query = CreateQuery(@"
query { query {
@ -419,7 +352,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}", contentId); }", contentId);
A.CallTo(() => contentQuery.FindAsync(MatchsContentContext(), schemaId.Id.ToString(), contentId, 3, A<CancellationToken>._)) A.CallTo(() => contentQuery.FindAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(), contentId, 3, A<CancellationToken>._))
.Returns(content); .Returns(content);
var result = await ExecuteAsync(new ExecutionOptions { Query = query }); var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -439,10 +372,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_also_fetch_referenced_contents_if_field_is_included_in_query() public async Task Should_also_fetch_referenced_contents_if_field_is_included_in_query()
{ {
var contentRefId = DomainId.NewGuid(); var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(schemaRefId1, contentRefId, "ref1-field", "ref1"); var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "schemaRef1Field", "ref1");
var contentId = DomainId.NewGuid(); var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, contentRefId, DomainId.Empty); var content = TestContent.Create(contentId, contentRefId);
var query = CreateQuery(@" var query = CreateQuery(@"
query { query {
@ -453,7 +386,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
iv { iv {
id id
data { data {
ref1Field { schemaRef1Field {
iv iv
} }
} }
@ -489,7 +422,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
id = contentRefId, id = contentRefId,
data = new data = new
{ {
ref1Field = new schemaRef1Field = new
{ {
iv = "ref1" iv = "ref1"
} }
@ -509,10 +442,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_also_fetch_referencing_contents_if_field_is_included_in_query() public async Task Should_also_fetch_referencing_contents_if_field_is_included_in_query()
{ {
var contentRefId = DomainId.NewGuid(); var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(schemaRefId1, contentRefId, "ref1-field", "ref1"); var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "ref1-field", "ref1");
var contentId = DomainId.NewGuid(); var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, contentRefId, DomainId.Empty); var content = TestContent.Create(contentId, contentRefId);
var query = CreateQuery(@" var query = CreateQuery(@"
query { query {
@ -521,8 +454,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
referencingMySchemaContents(top: 30, skip: 5) { referencingMySchemaContents(top: 30, skip: 5) {
id id
data { data {
myString { myLocalizedString {
de de_DE
} }
} }
} }
@ -552,9 +485,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
id = contentId, id = contentId,
data = new data = new
{ {
myString = new myLocalizedString = new
{ {
de = "value" de_DE = "de-DE"
} }
} }
} }
@ -570,10 +503,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_also_fetch_referencing_contents_with_total_if_field_is_included_in_query() public async Task Should_also_fetch_referencing_contents_with_total_if_field_is_included_in_query()
{ {
var contentRefId = DomainId.NewGuid(); var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(schemaRefId1, contentRefId, "ref1-field", "ref1"); var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "ref1-field", "ref1");
var contentId = DomainId.NewGuid(); var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, contentRefId, DomainId.Empty); var content = TestContent.Create(contentId, contentRefId);
var query = CreateQuery(@" var query = CreateQuery(@"
query { query {
@ -584,8 +517,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
items { items {
id id
data { data {
myString { myLocalizedString {
de de_DE
} }
} }
} }
@ -619,9 +552,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
id = contentId, id = contentId,
data = new data = new
{ {
myString = new myLocalizedString = new
{ {
de = "value" de_DE = "de-DE"
} }
} }
} }
@ -638,10 +571,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_also_fetch_union_contents_if_field_is_included_in_query() public async Task Should_also_fetch_union_contents_if_field_is_included_in_query()
{ {
var contentRefId = DomainId.NewGuid(); var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(schemaRefId1, contentRefId, "ref1-field", "ref1"); var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "schemaRef1Field", "ref1");
var contentId = DomainId.NewGuid(); var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, contentRefId, DomainId.Empty); var content = TestContent.Create(contentId, contentRefId);
var query = CreateQuery(@" var query = CreateQuery(@"
query { query {
@ -655,7 +588,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
... on MyRefSchema1 { ... on MyRefSchema1 {
data { data {
ref1Field { schemaRef1Field {
iv iv
} }
} }
@ -693,7 +626,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
id = contentRefId, id = contentRefId,
data = new data = new
{ {
ref1Field = new schemaRef1Field = new
{ {
iv = "ref1" iv = "ref1"
} }
@ -714,10 +647,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_also_fetch_referenced_assets_if_field_is_included_in_query() public async Task Should_also_fetch_referenced_assets_if_field_is_included_in_query()
{ {
var assetRefId = DomainId.NewGuid(); var assetRefId = DomainId.NewGuid();
var assetRef = TestAsset.Create(appId, assetRefId); var assetRef = TestAsset.Create(assetRefId);
var contentId = DomainId.NewGuid(); var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, DomainId.Empty, assetRefId); var content = TestContent.Create(contentId, assetId: assetRefId);
var query = CreateQuery(@" var query = CreateQuery(@"
query { query {
@ -772,7 +705,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_not_return_data_if_field_not_part_of_content() public async Task Should_not_return_data_if_field_not_part_of_content()
{ {
var contentId = DomainId.NewGuid(); var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, DomainId.Empty, DomainId.Empty, new ContentData()); var content = TestContent.Create(contentId, data: new ContentData());
var query = CreateQuery(@" var query = CreateQuery(@"
query { query {
@ -808,13 +741,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
.Replace("'", "\"") .Replace("'", "\"")
.Replace("<ID>", id.ToString()) .Replace("<ID>", id.ToString())
.Replace("<FIELDS_ASSET>", TestAsset.AllFields) .Replace("<FIELDS_ASSET>", TestAsset.AllFields)
.Replace("<FIELDS_CONTENT>", TestContent.AllFields); .Replace("<FIELDS_CONTENT>", TestContent.AllFields)
.Replace("<FIELDS_CONTENT_FLAT>", TestContent.AllFlatFields);
} }
private Context MatchsAssetContext() private Context MatchsAssetContext()
{ {
return A<Context>.That.Matches(x => return A<Context>.That.Matches(x =>
x.App == app && x.App == TestApp.Default &&
x.ShouldSkipCleanup() && x.ShouldSkipCleanup() &&
x.ShouldSkipContentEnrichment() && x.ShouldSkipContentEnrichment() &&
x.User == requestContext.User); x.User == requestContext.User);
@ -823,7 +757,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
private Context MatchsContentContext() private Context MatchsContentContext()
{ {
return A<Context>.That.Matches(x => return A<Context>.That.Matches(x =>
x.App == app && x.App == TestApp.Default &&
x.ShouldSkipCleanup() && x.ShouldSkipCleanup() &&
x.ShouldSkipContentEnrichment() && x.ShouldSkipContentEnrichment() &&
x.User == requestContext.User); x.User == requestContext.User);

110
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
@ -19,9 +18,7 @@ using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Caching; using Squidex.Caching;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives;
@ -49,23 +46,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
protected readonly ICommandBus commandBus = A.Fake<ICommandBus>(); protected readonly ICommandBus commandBus = A.Fake<ICommandBus>();
protected readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>(); protected readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
protected readonly IUserResolver userResolver = A.Fake<IUserResolver>(); protected readonly IUserResolver userResolver = A.Fake<IUserResolver>();
protected readonly ISchemaEntity schema;
protected readonly ISchemaEntity schemaRef1;
protected readonly ISchemaEntity schemaRef2;
protected readonly ISchemaEntity schemaInvalidName;
protected readonly IAppEntity app;
protected readonly Context requestContext; protected readonly Context requestContext;
protected readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
protected readonly NamedId<DomainId> schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema");
protected readonly NamedId<DomainId> schemaRefId1 = NamedId.Of(DomainId.NewGuid(), "my-ref-schema1");
protected readonly NamedId<DomainId> schemaRefId2 = NamedId.Of(DomainId.NewGuid(), "my-ref-schema2");
protected readonly NamedId<DomainId> schemaInvalidNameId = NamedId.Of(DomainId.NewGuid(), "content");
protected readonly CachingGraphQLService sut;
public GraphQLTestBase() public GraphQLTestBase()
{ {
app = Mocks.App(appId, Language.DE, Language.GermanGermany);
A.CallTo(() => userResolver.QueryManyAsync(A<string[]>._)) A.CallTo(() => userResolver.QueryManyAsync(A<string[]>._))
.ReturnsLazily(x => .ReturnsLazily(x =>
{ {
@ -76,81 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return Task.FromResult(users.ToDictionary(x => x.Id)); return Task.FromResult(users.ToDictionary(x => x.Id));
}); });
var schemaDef = requestContext = new Context(Mocks.FrontendUser(), TestApp.Default);
new Schema(schemaId.Name)
.Publish()
.AddJson(1, "my-json", Partitioning.Invariant,
new JsonFieldProperties())
.AddString(2, "my-string", Partitioning.Language,
new StringFieldProperties())
.AddString(3, "my-string2", Partitioning.Invariant,
new StringFieldProperties())
.AddString(4, "my-localized", Partitioning.Language,
new StringFieldProperties())
.AddNumber(5, "my-number", Partitioning.Invariant,
new NumberFieldProperties())
.AddNumber(6, "my_number", Partitioning.Invariant,
new NumberFieldProperties())
.AddAssets(7, "my-assets", Partitioning.Invariant,
new AssetsFieldProperties())
.AddBoolean(8, "my-boolean", Partitioning.Invariant,
new BooleanFieldProperties())
.AddDateTime(9, "my-datetime", Partitioning.Invariant,
new DateTimeFieldProperties())
.AddReferences(10, "my-references", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = schemaRefId1.Id })
.AddReferences(11, "my-union", Partitioning.Invariant,
new ReferencesFieldProperties())
.AddReferences(12, "my-invalid", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = DomainId.NewGuid() })
.AddGeolocation(13, "my-geolocation", Partitioning.Invariant,
new GeolocationFieldProperties())
.AddComponent(14, "my-component", Partitioning.Invariant,
new ComponentFieldProperties())
.AddComponents(15, "my-components", Partitioning.Invariant,
new ComponentsFieldProperties())
.AddTags(16, "my-tags", Partitioning.Invariant,
new TagsFieldProperties())
.AddArray(17, "my-empty-array", Partitioning.Invariant, null,
new ArrayFieldProperties())
.AddNumber(50, "2_numbers", Partitioning.Invariant,
new NumberFieldProperties())
.AddNumber(51, "2-numbers", Partitioning.Invariant,
new NumberFieldProperties())
.AddNumber(52, "content", Partitioning.Invariant,
new NumberFieldProperties())
.AddArray(100, "my-array", Partitioning.Invariant, f => f
.AddBoolean(121, "nested-boolean")
.AddNumber(122, "nested-number")
.AddNumber(123, "nested_number"))
.SetScripts(new SchemaScripts { Query = "<query-script>" });
schema = Mocks.Schema(appId, schemaId, schemaDef);
var schemaRef1Def =
new Schema(schemaRefId1.Name)
.Publish()
.AddString(1, "ref1-field", Partitioning.Invariant);
schemaRef1 = Mocks.Schema(appId, schemaRefId1, schemaRef1Def);
var schemaRef2Def =
new Schema(schemaRefId2.Name)
.Publish()
.AddString(1, "ref2-field", Partitioning.Invariant);
schemaRef2 = Mocks.Schema(appId, schemaRefId2, schemaRef2Def);
var schemaInvalidNameDef =
new Schema(schemaInvalidNameId.Name)
.Publish()
.AddString(1, "my-field", Partitioning.Invariant);
schemaInvalidName = Mocks.Schema(appId, schemaInvalidNameId, schemaInvalidNameDef);
requestContext = new Context(Mocks.FrontendUser(), app);
sut = CreateSut();
} }
protected void AssertResult(object expected, ExecutionResult result) protected void AssertResult(object expected, ExecutionResult result)
@ -167,9 +77,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
if (permissionId != null) if (permissionId != null)
{ {
var permission = Permissions.ForApp(permissionId, app.Name, schemaId.Name).Id; var permission = Permissions.ForApp(permissionId, TestApp.Default.Name, TestSchemas.DefaultId.Name).Id;
context = new Context(Mocks.FrontendUser(permission: permission), app); context = new Context(Mocks.FrontendUser(permission: permission), TestApp.Default);
} }
return ExcecuteAsync(options, context); return ExcecuteAsync(options, context);
@ -177,6 +87,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
private Task<ExecutionResult> ExcecuteAsync(ExecutionOptions options, Context context) private Task<ExecutionResult> ExcecuteAsync(ExecutionOptions options, Context context)
{ {
var sut = CreateSut(TestSchemas.Default, TestSchemas.Ref1, TestSchemas.Ref2);
options.UserContext = ActivatorUtilities.CreateInstance<GraphQLExecutionContext>(sut.Services, context); options.UserContext = ActivatorUtilities.CreateInstance<GraphQLExecutionContext>(sut.Services, context);
var listener = sut.Services.GetService<DataLoaderDocumentListener>(); var listener = sut.Services.GetService<DataLoaderDocumentListener>();
@ -189,20 +101,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return sut.ExecuteAsync(options); return sut.ExecuteAsync(options);
} }
private CachingGraphQLService CreateSut() protected CachingGraphQLService CreateSut(params ISchemaEntity[] schemas)
{ {
var cache = new BackgroundCache(new MemoryCache(Options.Create(new MemoryCacheOptions()))); var cache = new BackgroundCache(new MemoryCache(Options.Create(new MemoryCacheOptions())));
var appProvider = A.Fake<IAppProvider>(); var appProvider = A.Fake<IAppProvider>();
A.CallTo(() => appProvider.GetSchemasAsync(appId.Id)) A.CallTo(() => appProvider.GetSchemasAsync(TestApp.Default.Id))
.Returns(new List<ISchemaEntity> .Returns(schemas.ToList());
{
schema,
schemaRef1,
schemaRef2,
schemaInvalidName
});
var dataLoaderContext = (IDataLoaderContextAccessor)new DataLoaderContextAccessor(); var dataLoaderContext = (IDataLoaderContextAccessor)new DataLoaderContextAccessor();
var dataLoaderListener = new DataLoaderDocumentListener(dataLoaderContext); var dataLoaderListener = new DataLoaderDocumentListener(dataLoaderContext);

25
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestApp.cs

@ -0,0 +1,25 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
public static class TestApp
{
public static readonly NamedId<DomainId> DefaultId = NamedId.Of(DomainId.NewGuid(), "my-app");
public static readonly IAppEntity Default;
static TestApp()
{
Default = Mocks.App(DefaultId, Language.DE, Language.GermanGermany);
}
}
}

12
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestAsset.cs

@ -21,15 +21,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
created created
createdBy createdBy
createdByUser { createdByUser {
id, id
email, email
displayName displayName
} }
lastModified lastModified
lastModifiedBy lastModifiedBy
lastModifiedByUser { lastModifiedByUser {
id, id
email, email
displayName displayName
} }
url url
@ -52,14 +52,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
metadata metadata
slug"; slug";
public static IEnrichedAssetEntity Create(NamedId<DomainId> appId, DomainId id) public static IEnrichedAssetEntity Create(DomainId id)
{ {
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();
var asset = new AssetEntity var asset = new AssetEntity
{ {
Id = id, Id = id,
AppId = appId, AppId = TestApp.DefaultId,
Version = 1, Version = 1,
Created = now, Created = now,
CreatedBy = RefToken.User("user1"), CreatedBy = RefToken.User("user1"),

493
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs

@ -8,10 +8,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Text;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
@ -23,105 +21,150 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
created created
createdBy createdBy
createdByUser { createdByUser {
id, id
email, email
displayName displayName
} }
lastModified lastModified
lastModifiedBy lastModifiedBy
lastModifiedByUser { lastModifiedByUser {
id, id
email, email
displayName displayName
} }
status status
statusColor statusColor
url url
data { data {
gql_2Numbers { myJson {
iv iv
ivValue: iv(path: ""value"")
}
myString {
iv
}
myLocalizedString {
de_DE
}
myNumber {
iv
}
myBoolean {
iv
}
myDatetime {
iv
}
myGeolocation {
iv
}
myComponent__Dynamic {
iv
}
myComponent {
iv {
schemaId
schemaRef1Field
} }
gql_2Numbers2 { }
iv myComponents__Dynamic {
iv
}
myComponents {
iv {
__typename
... on MyRefSchema1Component {
schemaId
schemaRef1Field
}
... on MyRefSchema2Component {
schemaId
schemaRef2Field
}
} }
content { }
iv myTags {
iv
}
myArray {
iv {
nestedNumber
nestedBoolean
} }
myString { }
de }";
}
myString2 { public const string AllFlatFields = @"
iv id
} version
myNumber { created
iv createdBy
} createdByUser {
myNumber2 { id
iv email
} displayName
myBoolean { }
iv lastModified
} lastModifiedBy
myDatetime { lastModifiedByUser {
iv id
} email
myJson { displayName
iv }
} status
myGeolocation { statusColor
iv url
} flatData {
myComponent__Dynamic { myJson
iv myJsonValue: myJson(path: ""value"")
} myString
myComponents__Dynamic { myLocalizedString
iv myNumber
} myBoolean
myTags { myDatetime
iv myGeolocation
} myComponent__Dynamic
myLocalized { myComponent {
de_DE schemaId
schemaRef1Field
}
myComponents__Dynamic
myComponents {
__typename
... on MyRefSchema1Component {
schemaId
schemaRef1Field
} }
myArray { ... on MyRefSchema2Component {
iv { schemaId
nestedNumber schemaRef2Field
nestedNumber2
nestedBoolean
}
} }
}
myTags
myArray {
nestedNumber
nestedBoolean
}
}"; }";
public static IEnrichedContentEntity Create(NamedId<DomainId> appId, NamedId<DomainId> schemaId, DomainId id, DomainId refId, DomainId assetId, ContentData? data = null) public static IEnrichedContentEntity Create(DomainId id, DomainId refId = default, DomainId assetId = default, ContentData? data = null)
{ {
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();
data ??= data ??=
new ContentData() new ContentData()
.AddField("my-string", .AddField("my-localized-string",
new ContentFieldData() new ContentFieldData()
.AddLocalized("de", "value")) .AddLocalized("de-DE", "de-DE"))
.AddField("my-string2", .AddField("my-string",
new ContentFieldData() new ContentFieldData()
.AddInvariant(null)) .AddInvariant(null))
.AddField("my-assets", .AddField("my-assets",
new ContentFieldData() new ContentFieldData()
.AddInvariant(JsonValue.Array(assetId.ToString()))) .AddInvariant(JsonValue.Array(assetId.ToString())))
.AddField("2_numbers",
new ContentFieldData()
.AddInvariant(22))
.AddField("2-numbers",
new ContentFieldData()
.AddInvariant(23))
.AddField("content",
new ContentFieldData()
.AddInvariant(24))
.AddField("my-number", .AddField("my-number",
new ContentFieldData() new ContentFieldData()
.AddInvariant(1.0)) .AddInvariant(1.0))
.AddField("my_number",
new ContentFieldData()
.AddInvariant(null))
.AddField("my-boolean", .AddField("my-boolean",
new ContentFieldData() new ContentFieldData()
.AddInvariant(true)) .AddInvariant(true))
@ -147,48 +190,44 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
new ContentFieldData() new ContentFieldData()
.AddInvariant( .AddInvariant(
JsonValue.Object() JsonValue.Object()
.Add(Component.Discriminator, DomainId.Empty) .Add(Component.Discriminator, TestSchemas.Ref1.Id)
.Add("value1", 100) .Add("schemaRef1Field", "Component1")))
.Add("value2", 200)))
.AddField("my-components", .AddField("my-components",
new ContentFieldData() new ContentFieldData()
.AddInvariant( .AddInvariant(
JsonValue.Array( JsonValue.Array(
JsonValue.Object() JsonValue.Object()
.Add(Component.Discriminator, DomainId.Empty) .Add(Component.Discriminator, TestSchemas.Ref1.Id)
.Add("value1", 100) .Add("schemaRef1Field", "Component1"),
.Add("value2", 200)))) JsonValue.Object()
.Add(Component.Discriminator, TestSchemas.Ref2.Id)
.Add("schemaRef2Field", "Component2"))))
.AddField("my-json", .AddField("my-json",
new ContentFieldData() new ContentFieldData()
.AddInvariant( .AddInvariant(
JsonValue.Object() JsonValue.Object()
.Add("value", 1))) .Add("value", 1)))
.AddField("my-localized",
new ContentFieldData()
.AddLocalized("de-DE", "de-DE"))
.AddField("my-array", .AddField("my-array",
new ContentFieldData() new ContentFieldData()
.AddInvariant(JsonValue.Array( .AddInvariant(JsonValue.Array(
JsonValue.Object() JsonValue.Object()
.Add("nested-number", 10) .Add("nested-number", 10)
.Add("nested_number", null)
.Add("nested-boolean", true), .Add("nested-boolean", true),
JsonValue.Object() JsonValue.Object()
.Add("nested-number", 20) .Add("nested-number", 20)
.Add("nested_number", null)
.Add("nested-boolean", false)))); .Add("nested-boolean", false))));
var content = new ContentEntity var content = new ContentEntity
{ {
Id = id, Id = id,
AppId = appId, AppId = TestApp.DefaultId,
Version = 1, Version = 1,
Created = now, Created = now,
CreatedBy = RefToken.User("user1"), CreatedBy = RefToken.User("user1"),
LastModified = now, LastModified = now,
LastModifiedBy = RefToken.Client("client1"), LastModifiedBy = RefToken.Client("client1"),
Data = data, Data = data,
SchemaId = schemaId, SchemaId = TestSchemas.DefaultId,
Status = Status.Draft, Status = Status.Draft,
StatusColor = "red" StatusColor = "red"
}; };
@ -209,6 +248,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var content = new ContentEntity var content = new ContentEntity
{ {
Id = id, Id = id,
AppId = TestApp.DefaultId,
Version = 1, Version = 1,
Created = now, Created = now,
CreatedBy = RefToken.User("user1"), CreatedBy = RefToken.User("user1"),
@ -248,42 +288,62 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
status = "DRAFT", status = "DRAFT",
statusColor = "red", statusColor = "red",
url = $"contents/my-schema/{content.Id}", url = $"contents/my-schema/{content.Id}",
data = Data(content, false) data = Data(content)
}; };
} }
public static object Data(IContentEntity content, bool input, DomainId refId = default, DomainId assetId = default) public static object FlatResponse(IEnrichedContentEntity content)
{ {
var result = new Dictionary<string, object> return new
{ {
["gql_2Numbers"] = new id = content.Id,
version = 1,
created = content.Created,
createdBy = content.CreatedBy.ToString(),
createdByUser = new
{ {
iv = 22.0 id = content.CreatedBy.Identifier,
email = $"{content.CreatedBy.Identifier}@email.com",
displayName = $"name_{content.CreatedBy.Identifier}"
}, },
["gql_2Numbers2"] = new lastModified = content.LastModified,
lastModifiedBy = content.LastModifiedBy.ToString(),
lastModifiedByUser = new
{ {
iv = 23.0 id = content.LastModifiedBy.Identifier,
email = $"{content.LastModifiedBy}",
displayName = content.LastModifiedBy.Identifier
}, },
["content"] = new status = "DRAFT",
statusColor = "red",
url = $"contents/my-schema/{content.Id}",
flatData = FlatData(content)
};
}
public static object Input(IContentEntity content, DomainId refId = default, DomainId assetId = default)
{
var result = new Dictionary<string, object>
{
["myJson"] = new
{ {
iv = 24.0 iv = new
{
value = 1
},
}, },
["myString"] = new ["myString"] = new
{ {
de = "value" iv = (string?)null,
}, },
["myString2"] = new ["myLocalizedString"] = new
{ {
iv = (object?)null de_DE = "de-DE"
}, },
["myNumber"] = new ["myNumber"] = new
{ {
iv = 1.0 iv = 1.0
}, },
["myNumber2"] = new
{
iv = (object?)null
},
["myBoolean"] = new ["myBoolean"] = new
{ {
iv = true iv = true
@ -292,13 +352,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
iv = content.LastModified.ToString() iv = content.LastModified.ToString()
}, },
["myJson"] = new
{
iv = new
{
value = 1
}
},
["myGeolocation"] = new ["myGeolocation"] = new
{ {
iv = new iv = new
@ -307,25 +360,28 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
longitude = 20 longitude = 20
} }
}, },
["myComponent".AsDynamic(input)] = new ["myComponent"] = new
{ {
iv = new iv = new Dictionary<string, object>
{ {
schemaId = DomainId.Empty.ToString(), ["schemaId"] = TestSchemas.Ref1.Id.ToString(),
value1 = 100, ["schemaRef1Field"] = "Component1"
value2 = 200
} }
}, },
["myComponents".AsDynamic(input)] = new ["myComponents"] = new
{ {
iv = new[] iv = new[]
{ {
new new Dictionary<string, object>
{ {
schemaId = DomainId.Empty.ToString(), ["schemaId"] = TestSchemas.Ref1.Id.ToString(),
value1 = 100, ["schemaRef1Field"] = "Component1"
value2 = 200 },
} new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaRef2Field"] = "Component2"
},
} }
}, },
["myTags"] = new ["myTags"] = new
@ -336,10 +392,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
"tag2" "tag2"
} }
}, },
["myLocalized"] = new
{
de_DE = "de-DE"
},
["myArray"] = new ["myArray"] = new
{ {
iv = new[] iv = new[]
@ -347,13 +399,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
new new
{ {
nestedNumber = 10.0, nestedNumber = 10.0,
nestedNumber2 = (object?)null,
nestedBoolean = true nestedBoolean = true
}, },
new new
{ {
nestedNumber = 20.0, nestedNumber = 20.0,
nestedNumber2 = (object?)null,
nestedBoolean = false nestedBoolean = false
} }
} }
@ -392,5 +442,204 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return result; return result;
} }
private static object Data(IContentEntity content)
{
var result = new Dictionary<string, object>
{
["myJson"] = new
{
iv = new
{
value = 1
},
ivValue = 1
},
["myString"] = new
{
iv = (string?)null,
},
["myLocalizedString"] = new
{
de_DE = "de-DE"
},
["myNumber"] = new
{
iv = 1.0
},
["myBoolean"] = new
{
iv = true
},
["myDatetime"] = new
{
iv = content.LastModified.ToString()
},
["myGeolocation"] = new
{
iv = new
{
latitude = 10,
longitude = 20
}
},
["myComponent__Dynamic"] = new
{
iv = new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
}
},
["myComponent"] = new
{
iv = new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
}
},
["myComponents__Dynamic"] = new
{
iv = new[]
{
new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
},
new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaRef2Field"] = "Component2"
},
}
},
["myComponents"] = new
{
iv = new object[]
{
new Dictionary<string, object>
{
["__typename"] = "MyRefSchema1Component",
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
},
new Dictionary<string, object>
{
["__typename"] = "MyRefSchema2Component",
["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaRef2Field"] = "Component2"
},
}
},
["myTags"] = new
{
iv = new[]
{
"tag1",
"tag2"
}
},
["myArray"] = new
{
iv = new[]
{
new
{
nestedNumber = 10.0,
nestedBoolean = true
},
new
{
nestedNumber = 20.0,
nestedBoolean = false
}
}
}
};
return result;
}
private static object FlatData(IContentEntity content)
{
var result = new Dictionary<string, object?>
{
["myJson"] = new
{
value = 1
},
["myJsonValue"] = 1,
["myString"] = null,
["myLocalizedString"] = "de-DE",
["myNumber"] = 1.0,
["myBoolean"] = true,
["myDatetime"] = content.LastModified.ToString(),
["myGeolocation"] = new
{
latitude = 10,
longitude = 20
},
["myComponent__Dynamic"] = new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
},
["myComponent"] = new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
},
["myComponents__Dynamic"] = new[]
{
new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
},
new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaRef2Field"] = "Component2"
},
},
["myComponents"] = new object[]
{
new Dictionary<string, object>
{
["__typename"] = "MyRefSchema1Component",
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
},
new Dictionary<string, object>
{
["__typename"] = "MyRefSchema2Component",
["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaRef2Field"] = "Component2"
},
},
["myTags"] = new[]
{
"tag1",
"tag2"
},
["myArray"] = new[]
{
new
{
nestedNumber = 10.0,
nestedBoolean = true
},
new
{
nestedNumber = 20.0,
nestedBoolean = false
}
}
};
return result;
}
} }
} }

76
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs

@ -0,0 +1,76 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
public static class TestSchemas
{
public static readonly NamedId<DomainId> DefaultId = NamedId.Of(DomainId.NewGuid(), "my-schema");
public static readonly NamedId<DomainId> Ref1Id = NamedId.Of(DomainId.NewGuid(), "my-ref-schema1");
public static readonly NamedId<DomainId> Ref2Id = NamedId.Of(DomainId.NewGuid(), "my-ref-schema2");
public static readonly ISchemaEntity Default;
public static readonly ISchemaEntity Ref1;
public static readonly ISchemaEntity Ref2;
static TestSchemas()
{
Ref1 = Mocks.Schema(TestApp.DefaultId, Ref1Id,
new Schema(Ref1Id.Name)
.Publish()
.AddString(1, "schemaRef1Field", Partitioning.Invariant));
Ref2 = Mocks.Schema(TestApp.DefaultId, Ref2Id,
new Schema(Ref2Id.Name)
.Publish()
.AddString(1, "schemaRef2Field", Partitioning.Invariant));
Default = Mocks.Schema(TestApp.DefaultId, DefaultId,
new Schema(DefaultId.Name)
.Publish()
.AddJson(1, "my-json", Partitioning.Invariant,
new JsonFieldProperties())
.AddString(2, "my-string", Partitioning.Invariant,
new StringFieldProperties())
.AddString(3, "my-localized-string", Partitioning.Language,
new StringFieldProperties())
.AddNumber(4, "my-number", Partitioning.Invariant,
new NumberFieldProperties())
.AddAssets(5, "my-assets", Partitioning.Invariant,
new AssetsFieldProperties())
.AddBoolean(6, "my-boolean", Partitioning.Invariant,
new BooleanFieldProperties())
.AddDateTime(7, "my-datetime", Partitioning.Invariant,
new DateTimeFieldProperties())
.AddReferences(8, "my-references", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = Ref1Id.Id })
.AddReferences(9, "my-union", Partitioning.Invariant,
new ReferencesFieldProperties())
.AddGeolocation(10, "my-geolocation", Partitioning.Invariant,
new GeolocationFieldProperties())
.AddComponent(11, "my-component", Partitioning.Invariant,
new ComponentFieldProperties { SchemaId = Ref1Id.Id })
.AddComponents(12, "my-components", Partitioning.Invariant,
new ComponentsFieldProperties { SchemaIds = ImmutableList.Create(Ref1.Id, Ref2.Id) })
.AddTags(13, "my-tags", Partitioning.Invariant,
new TagsFieldProperties())
.AddArray(100, "my-array", Partitioning.Invariant, f => f
.AddBoolean(121, "nested-boolean",
new BooleanFieldProperties())
.AddNumber(122, "nested-number",
new NumberFieldProperties()))
.SetScripts(new SchemaScripts { Query = "<query-script>" }));
}
}
}

1884
frontend/package-lock.json

File diff suppressed because it is too large

94
frontend/package.json

@ -14,37 +14,37 @@
"build": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js --config app-config/webpack.config.js --env production", "build": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js --config app-config/webpack.config.js --env production",
"build:clean": "rimraf wwwroot/build", "build:clean": "rimraf wwwroot/build",
"build:analyze": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js --config app-config/webpack.config.js --env production --env analyze", "build:analyze": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js --config app-config/webpack.config.js --env production --env analyze",
"postinstall": "npx patch-package && ngcc", "postinstall": "npx patch-package && ngcc --properties es2015 browser module main --create-ivy-entry-points",
"ngcc": "ngcc" "ngcc": "ngcc --properties es2015 browser module main --create-ivy-entry-points"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "12.0.0", "@angular/animations": "12.0.2",
"@angular/cdk": "12.0.0", "@angular/cdk": "12.0.2",
"@angular/common": "12.0.0", "@angular/common": "12.0.2",
"@angular/core": "12.0.0", "@angular/core": "12.0.2",
"@angular/forms": "12.0.0", "@angular/forms": "12.0.2",
"@angular/localize": "12.0.0", "@angular/localize": "12.0.2",
"@angular/platform-browser": "12.0.0", "@angular/platform-browser": "12.0.2",
"@angular/platform-browser-dynamic": "12.0.0", "@angular/platform-browser-dynamic": "12.0.2",
"@angular/platform-server": "12.0.0", "@angular/platform-server": "12.0.2",
"@angular/router": "12.0.0", "@angular/router": "12.0.2",
"@egjs/hammerjs": "2.0.17", "@egjs/hammerjs": "2.0.17",
"ace-builds": "1.4.12", "ace-builds": "1.4.12",
"angular-gridster2": "11.2.0", "angular-gridster2": "12.0.0",
"angular-mentions": "1.3.0", "angular-mentions": "1.3.0",
"angular2-chartjs": "0.5.1", "angular2-chartjs": "0.5.1",
"babel-polyfill": "6.26.0", "babel-polyfill": "6.26.0",
"bootstrap": "4.6.0", "bootstrap": "4.6.0",
"codemirror-graphql": "1.0.1", "codemirror-graphql": "1.0.2",
"core-js": "3.12.1", "core-js": "3.13.0",
"cropperjs": "2.0.0-alpha.1", "cropperjs": "2.0.0-alpha.1",
"date-fns": "2.21.3", "date-fns": "2.21.3",
"font-awesome": "4.7.0", "font-awesome": "4.7.0",
"graphiql": "1.4.1", "graphiql": "1.4.2",
"graphql": "15.5.0", "graphql": "15.5.0",
"image-focus": "1.2.0", "image-focus": "1.2.0",
"keycharm": "0.4.0", "keycharm": "0.4.0",
"marked": "2.0.3", "marked": "2.0.5",
"mersenne-twister": "1.1.0", "mersenne-twister": "1.1.0",
"mousetrap": "1.6.5", "mousetrap": "1.6.5",
"ngx-color-picker": "11.0.0", "ngx-color-picker": "11.0.0",
@ -55,10 +55,10 @@
"prop-types": "15.7.2", "prop-types": "15.7.2",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"rxjs": "7.0.1", "rxjs": "7.1.0",
"simplemde": "1.11.2", "simplemde": "1.11.2",
"slugify": "1.5.3", "slugify": "1.5.3",
"tinymce": "5.8.0", "tinymce": "5.8.1",
"tslib": "2.2.0", "tslib": "2.2.0",
"video.js": "7.11.8", "video.js": "7.11.8",
"vis-data": "7.1.2", "vis-data": "7.1.2",
@ -67,38 +67,38 @@
"zone.js": "0.11.4" "zone.js": "0.11.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-optimizer": "0.1200.0", "@angular-devkit/build-optimizer": "0.1200.2",
"@angular/compiler": "12.0.0", "@angular/compiler": "12.0.2",
"@angular/compiler-cli": "12.0.0", "@angular/compiler-cli": "12.0.2",
"@ngtools/webpack": "12.0.0", "@ngtools/webpack": "12.0.2",
"@types/codemirror": "0.0.108", "@types/codemirror": "0.0.108",
"@types/core-js": "2.5.4", "@types/core-js": "2.5.4",
"@types/jasmine": "3.7.4", "@types/jasmine": "3.7.5",
"@types/marked": "2.0.2", "@types/marked": "2.0.3",
"@types/mersenne-twister": "1.1.2", "@types/mersenne-twister": "1.1.2",
"@types/mousetrap": "1.6.8", "@types/mousetrap": "1.6.8",
"@types/node": "15.3.0", "@types/node": "15.6.1",
"@types/react": "17.0.5", "@types/react": "17.0.8",
"@types/react-dom": "17.0.5", "@types/react-dom": "17.0.5",
"@types/simplemde": "1.11.7", "@types/simplemde": "1.11.7",
"@types/tapable": "2.2.2", "@types/tapable": "2.2.2",
"@types/tinymce": "4.6.1", "@types/tinymce": "4.6.1",
"@types/ws": "^7.4.4", "@types/ws": "7.4.4",
"@typescript-eslint/eslint-plugin": "^4.23.0", "@typescript-eslint/eslint-plugin": "4.25.0",
"@typescript-eslint/parser": "^4.23.0", "@typescript-eslint/parser": "4.25.0",
"browserslist": "4.16.6", "browserslist": "4.16.6",
"caniuse-lite": "1.0.30001228", "caniuse-lite": "1.0.30001230",
"circular-dependency-plugin": "5.2.2", "circular-dependency-plugin": "5.2.2",
"codelyzer": "6.0.2", "codelyzer": "6.0.2",
"copy-webpack-plugin": "8.1.1", "copy-webpack-plugin": "9.0.0",
"css-loader": "5.2.4", "css-loader": "5.2.6",
"cssnano": "5.0.2", "cssnano": "5.0.4",
"entities": "2.2.0", "entities": "2.2.0",
"eslint": "^7.26.0", "eslint": "7.27.0",
"eslint-config-airbnb-typescript": "^12.3.1", "eslint-config-airbnb-typescript": "12.3.1",
"eslint-plugin-import": "^2.23.1", "eslint-plugin-import": "2.23.3",
"eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-jsx-a11y": "6.4.1",
"eslint-webpack-plugin": "^2.5.4", "eslint-webpack-plugin": "2.5.4",
"file-loader": "6.2.0", "file-loader": "6.2.0",
"html-loader": "2.1.2", "html-loader": "2.1.2",
"html-webpack-plugin": "5.3.1", "html-webpack-plugin": "5.3.1",
@ -116,30 +116,30 @@
"karma-sourcemap-loader": "0.3.8", "karma-sourcemap-loader": "0.3.8",
"karma-webpack": "5.0.0", "karma-webpack": "5.0.0",
"mini-css-extract-plugin": "1.6.0", "mini-css-extract-plugin": "1.6.0",
"optimize-css-assets-webpack-plugin": "5.0.4", "optimize-css-assets-webpack-plugin": "6.0.0",
"postcss-import": "14.0.2", "postcss-import": "14.0.2",
"postcss-loader": "5.3.0", "postcss-loader": "5.3.0",
"postcss-preset-env": "6.7.0", "postcss-preset-env": "6.7.0",
"raw-loader": "4.0.2", "raw-loader": "4.0.2",
"resize-observer-polyfill": "1.5.1", "resize-observer-polyfill": "1.5.1",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"sass": "^1.32.13", "sass": "1.34.0",
"sass-lint": "1.13.1", "sass-lint": "1.13.1",
"sass-lint-webpack": "1.0.3", "sass-lint-webpack": "1.0.3",
"sass-loader": "11.1.1", "sass-loader": "11.1.1",
"style-loader": "2.0.0", "style-loader": "2.0.0",
"sugarss": "3.0.3", "sugarss": "3.0.3",
"terser": "^5.7.0", "terser": "5.7.0",
"terser-webpack-plugin": "5.1.2", "terser-webpack-plugin": "5.1.2",
"ts-loader": "9.1.2", "ts-loader": "9.2.2",
"tsconfig-paths-webpack-plugin": "3.5.1", "tsconfig-paths-webpack-plugin": "3.5.1",
"typemoq": "2.1.0", "typemoq": "2.1.0",
"typescript": "4.2.2", "typescript": "4.3.2",
"underscore": "1.13.1", "underscore": "1.13.1",
"webpack": "5.37.0", "webpack": "5.38.0",
"webpack-bundle-analyzer": "4.4.1", "webpack-bundle-analyzer": "4.4.2",
"webpack-cli": "4.7.0", "webpack-cli": "4.7.0",
"webpack-dev-server": "3.11.2", "webpack-dev-server": "3.11.2",
"webpack-filter-warnings-plugin": "^1.2.1" "webpack-filter-warnings-plugin": "1.2.1"
} }
} }

Loading…
Cancel
Save