Merged PR 211: Add csharp classes to SVGLayoutDesigner and add tests

This commit is contained in:
Eric Nguyen 2022-10-10 09:28:47 +00:00
parent 24e47ae240
commit f74af69291
40 changed files with 1346 additions and 13 deletions

View file

@ -8,7 +8,6 @@ steps:
commands: commands:
- node ./test-server/http.js & - node ./test-server/http.js &
- npm i - npm i
- npm run test:nowatch
- npm run build - npm run build
--- ---

41
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/csharp/SVGLDLibs/SVGLDWebAPI/SVGLDWebAPI.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/csharp/SVGLDLibs/SVGLDWebAPI/SVGLDWebAPI.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/csharp/SVGLDLibs/SVGLDWebAPI/SVGLDWebAPI.csproj"
],
"problemMatcher": "$msCompile"
}
]
}

View file

@ -10,12 +10,12 @@ An svg layout designer.
# Getting Started # Getting Started
Requierements : Requirements :
- NodeJS - `node` >= 16.x (>= 17.x to run vitest tests)
- npm - `npm`
- Chrome > 98 - pnpm (optional) reduce `node_modules` folder size by using symlinks
- pnpm (optional but recommanded unless you prefer having a huge `node_modules` directory)
- [`git-lfs`](https://git-lfs.github.com/) (in order to clone the documentation) - [`git-lfs`](https://git-lfs.github.com/) (in order to clone the documentation)
- `dotnet` (optional) used for api test
# Developping # Developping

View file

@ -34,12 +34,8 @@ steps:
- bash: | - bash: |
set -euo pipefail set -euo pipefail
node --version node --version
node ./test-server/http.js &
jobs
pnpm i pnpm i
pnpm run test:nowatch
pnpm run build pnpm run build
kill -2 %1 2>/dev/null
displayName: 'Test on Node.js 16.x LTS' displayName: 'Test on Node.js 16.x LTS'
- task: NodeTool@0 - task: NodeTool@0
@ -51,11 +47,13 @@ steps:
set -euo pipefail set -euo pipefail
node --version node --version
node ./test-server/http.js & node ./test-server/http.js &
dotnet run --project=./csharp/SVGLDLibs/SVGLDWebAPI/SVGLDWebAPI.csproj &
jobs jobs
sleep 5
pnpm i pnpm i
pnpm run test:nowatch pnpm run test:nowatch
pnpm run build pnpm run build
kill -2 %1 2>/dev/null kill -2 %1 %2 2>/dev/null
displayName: 'Test on Node.js 18.x Latest' displayName: 'Test on Node.js 18.x Latest'
- publish: $(System.DefaultWorkingDirectory)/dist - publish: $(System.DefaultWorkingDirectory)/dist

View file

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
trim_trailing_whitespace = true
end_of_line = crlf
indent_style = space
indent_size = 4
insert_final_newline = false

View file

@ -0,0 +1,21 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class ActionContainerModel
{
[DataMember(EmitDefaultValue = false)]
public string Id { get; set; }
[DataMember(EmitDefaultValue = false)]
public ImageModel CustomLogo { get; set; }
[DataMember(EmitDefaultValue = false)]
public string Label { get; set; }
[DataMember(EmitDefaultValue = false)]
public string Description { get; set; }
[DataMember(EmitDefaultValue = false)]
public string Action { get; set; }
[DataMember(EmitDefaultValue = false)]
public AddingBehaviorEnumModel AddingBehavior { get; set; }
}
}

View file

@ -0,0 +1,19 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public enum AddingBehaviorEnumModel
{
[EnumMember]
Append,
[EnumMember]
InsertInto,
[EnumMember]
Replace,
[EnumMember]
ReplaceParent,
}
}

View file

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class ApplicationStateModel
{
[DataMember(EmitDefaultValue = false)]
public string lastAction;
[DataMember(EmitDefaultValue = false)]
public ContainerModel mainContainer;
[DataMember(EmitDefaultValue = false)]
public string selectedContainerId;
[DataMember(EmitDefaultValue = false)]
public Dictionary<string, string> typeCounters;
[DataMember(EmitDefaultValue = false)]
public Dictionary<string, SymbolModel> symbols;
[DataMember(EmitDefaultValue = false)]
public string selectedSymbolId;
}
}

View file

@ -0,0 +1,192 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class AvailableContainerModel
{
/** type */
[DataMember(EmitDefaultValue = false, IsRequired = true)]
public string Type { get; set; }
/** displayed text */
[DataMember(EmitDefaultValue = false)]
public string DisplayedText { get; set; }
/** category */
[DataMember(EmitDefaultValue = false)]
public string Category { get; set; }
/** orientation */
[DataMember(EmitDefaultValue = false)]
public Orientation Orientation { get; set; }
/** horizontal offset */
[DataMember(EmitDefaultValue = false)]
public double? X { get; set; }
/** vertical offset */
[DataMember(EmitDefaultValue = false)]
public double? Y { get; set; }
/** width */
[DataMember(EmitDefaultValue = false)]
public double? Width { get; set; }
/** height */
[DataMember(EmitDefaultValue = false)]
public double? Height { get; set; }
/**
* Minimum width (min=1)
*/
[DataMember(EmitDefaultValue = false)]
public double? MinWidth { get; set; }
/**
* Maximum width
*/
[DataMember(EmitDefaultValue = false)]
public double? MaxWidth { get; set; }
/**
* Minimum height (min=1)
*/
[DataMember(EmitDefaultValue = false)]
public double? MinHeight { get; set; }
/**
* Maximum height
*/
[DataMember(EmitDefaultValue = false)]
public double? MaxHeight { get; set; }
/** margin */
[DataMember(EmitDefaultValue = false)]
public Margin Margin { get; set; }
/** true if anchor, false otherwise */
[DataMember(EmitDefaultValue = false)]
public bool? IsAnchor { get; set; }
/** true if flex, false otherwise */
[DataMember(EmitDefaultValue = false)]
public bool? IsFlex { get; set; }
/**
* Allow to use a Pattern to create the list of children
* Cannot be used with DefaultChildType,
* DefaultChildType will be disabled for this container and the children
*/
[DataMember(EmitDefaultValue = false)]
public string Pattern { get; set; }
/** Method used on container add */
[DataMember(EmitDefaultValue = false)]
public AddingBehaviorEnumModel? AddMethod { get; set; }
/**
* (optional)
* Disabled when Pattern is used.
*
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
* to draw some patterns that can be bind to the properties of the container
* Use {prop} to bind a property. Use {{ styleProp }} to use an object.
* Example :
* ```
* `<rect width="{width}" height="{height}" style="{style}"></rect>
* <rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
* <line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
* <line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
* `
* ```
*/
[DataMember(EmitDefaultValue = false)]
public string DefaultChildType { get; set; }
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
[DataMember(EmitDefaultValue = false)]
public PositionReferenceEnumModel? PositionReference { get; set; }
[DataMember(EmitDefaultValue = false)]
public bool? HideChildrenInTreeview { get; set; }
/** if true, show the dimension of the container */
[DataMember(EmitDefaultValue = false)]
public Position[] ShowSelfDimensions { get; set; }
/** if true show the overall dimensions of its children */
[DataMember(EmitDefaultValue = false)]
public Position[] ShowChildrenDimensions { get; set; }
/**
* if true, allows a parent dimension borrower to uses its x coordinate for as a reference point for a dimension
*/
[DataMember(EmitDefaultValue = false)]
public Orientation[] MarkPosition { get; set; }
/**
* if true, show a dimension from the edge of the container to end
* and insert dimensions mark
*/
[DataMember(EmitDefaultValue = false)]
public Position[] ShowDimensionWithMarks { get; set; }
/**
* if true, hide the entry in the sidebar (default: false)
*/
[DataMember(EmitDefaultValue = false)]
public bool? IsHidden { get; set; }
/**
* if true, hide the entry in the sidebar (default: false)
*/
[DataMember(EmitDefaultValue = false)]
public string[] Blacklist { get; set; }
/**
* Cannot be used with blacklist. Whitelist will be prioritized.
* To disable the whitelist, Whitelist must be undefined.
* Only allow a set of available container to be added inside
*/
[DataMember(EmitDefaultValue = false)]
public string[] Whitelist { get; set; }
/**
* List of possible actions shown on right-click
*/
[DataMember(EmitDefaultValue = false)]
public List<ActionContainerModel> Actions { get; set; }
/**
* (optional)
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
* to draw some patterns that can be bind to the properties of the container
* Use {prop} to bind a property. Use {{ styleProp }} to use an object.
* Example :
* ```
* `<rect width="{width}" height="{height}" style="{style}"></rect>
* <rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
* <line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
* <line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
* `
* ```
*/
[DataMember(EmitDefaultValue = false)]
public string CustomSVG { get; set; }
/**
* (optional)
* Style of the <rect>
*/
[DataMember(EmitDefaultValue = false)]
public CSSStyle Style { get; set; }
/**
* (optional)
* User data that can be used for data storage or custom SVG
*/
[DataMember(EmitDefaultValue = false)]
public Dictionary<string, string> UserData { get; set; }
}
}

View file

@ -0,0 +1,18 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class AvailableSymbolModel
{
[DataMember(EmitDefaultValue = false)]
public string Name { get; set; }
[DataMember(EmitDefaultValue = false)]
public ImageModel Image { get; set; }
[DataMember(EmitDefaultValue = false)]
public PositionReferenceEnumModel PositionReference { get; set; }
[DataMember(EmitDefaultValue = false)]
public double Width { get; set; }
[DataMember(EmitDefaultValue = false)]
public double Height { get; set; }
}
}

View file

@ -0,0 +1,19 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class CSSStyle
{
[DataMember(EmitDefaultValue = false)]
public double? strokeWidth;
[DataMember(EmitDefaultValue = false)]
public double? fillOpacity;
[DataMember(EmitDefaultValue = false)]
public string stroke;
[DataMember(EmitDefaultValue = false)]
public string fill;
}
}

View file

@ -0,0 +1,13 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class Category
{
[DataMember(EmitDefaultValue = false)]
public string Type { get; set; }
[DataMember(EmitDefaultValue = false)]
public string DisplayedText { get; set; }
}
}

View file

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class ConfigurationResponseModel
{
public ConfigurationResponseModel(AvailableContainerModel mainContainer)
{
MainContainer = mainContainer;
AvailableContainers = new List<AvailableContainerModel>();
AvailableSymbols = new List<AvailableSymbolModel>();
Categories = new List<Category>();
Patterns = new List<Pattern>();
}
[DataMember(EmitDefaultValue = false)]
public List<AvailableContainerModel> AvailableContainers { get; set; }
[DataMember(EmitDefaultValue = false)]
public List<AvailableSymbolModel> AvailableSymbols { get; set; }
[DataMember(EmitDefaultValue = false)]
public List<Category> Categories { get; set; }
[DataMember(EmitDefaultValue = false)]
public List<Pattern> Patterns { get; set; }
[DataMember(EmitDefaultValue = false, IsRequired = true)]
public AvailableContainerModel MainContainer { get; set; }
}
}

View file

@ -0,0 +1,17 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class ContainerModel
{
[DataMember(EmitDefaultValue = false)]
public List<ContainerModel> children;
[DataMember(EmitDefaultValue = false)]
public ContainerProperties properties;
[DataMember(EmitDefaultValue = false)]
public Dictionary<string, string> userData;
}
}

View file

@ -0,0 +1,153 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class ContainerProperties
{
/** id of the container */
[DataMember(EmitDefaultValue = false)]
public string id;
/** type matching the configuration on construction */
[DataMember(EmitDefaultValue = false)]
public string type;
/** id of the parent container (null when there is no parent) */
[DataMember(EmitDefaultValue = false)]
public string parentId;
/** id of the linked symbol ('' when there is no parent) */
[DataMember(EmitDefaultValue = false)]
public string linkedSymbolId;
/** Text displayed in the container */
[DataMember(EmitDefaultValue = false)]
public string displayedText;
/** orientation */
[DataMember(EmitDefaultValue =false)]
public Orientation orientation;
/** horizontal offset */
[DataMember(EmitDefaultValue = false)]
public double x;
/** vertical offset */
[DataMember(EmitDefaultValue = false)]
public double y;
/** margin */
[DataMember(EmitDefaultValue = false)]
public Margin margin;
/** width */
[DataMember(EmitDefaultValue = false)]
public double width;
/** height */
[DataMember(EmitDefaultValue = false)]
public double height;
/**
* Minimum width (min=1)
*/
[DataMember(EmitDefaultValue = false)]
public double minWidth;
/**
* Maximum width
*/
[DataMember(EmitDefaultValue = false)]
public double maxWidth;
/**
* Minimum height (min=1)
* Allows the container to set isRigidBody to false when it gets squeezed
* by an anchor
*/
[DataMember(EmitDefaultValue = false)]
public double minHeight;
/**
* Maximum height
*/
[DataMember(EmitDefaultValue = false)]
public double maxHeight;
/** true if anchor, false otherwise */
[DataMember(EmitDefaultValue = false)]
public bool isAnchor;
/** true if flex, false otherwise */
[DataMember(EmitDefaultValue = false)]
public bool isFlex;
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
[DataMember(EmitDefaultValue = false)]
public PositionReferenceEnumModel positionReference;
/** Hide the children in the treeview */
[DataMember(EmitDefaultValue = false)]
public bool hideChildrenInTreeview;
/** if true, show the dimension of the container */
[DataMember(EmitDefaultValue = false)]
public Position[] showSelfDimensions;
/** if true show the overall dimensions of its children */
[DataMember(EmitDefaultValue = false)]
public Position[] showChildrenDimensions;
/**
* if true, allows a parent dimension borrower to borrow its x coordinate
* as a reference point for a dimension
*/
[DataMember(EmitDefaultValue = false)]
public Orientation[] markPosition;
/**
* if true, show a dimension from the edge of the container to end
* and insert dimensions marks at lift up children (see liftDimensionToBorrower)
*/
[DataMember(EmitDefaultValue = false)]
public Position[] showDimensionWithMarks;
/**
* Warnings of a container
*/
[DataMember(EmitDefaultValue = false)]
public string warning;
/**
* (optional)
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
* to draw some patterns that can be bind to the properties of the container
* Use {prop} to bind a property. Use {{ styleProp }} to use an object.
* Example :
* ```
* `<rect width="{width}" height="{height}" style="{style}"></rect>
* <rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
* <line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
* <line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
* `
* ```
*/
[DataMember(EmitDefaultValue = false)]
public string customSVG;
/**
* (optional)
* Style of the <rect>
*/
[DataMember(EmitDefaultValue = false)]
public CSSStyle style;
/**
* (optional)
* User data that can be used for data storage or custom SVG
*/
[DataMember(EmitDefaultValue = false)]
public object userData;
}
}

View file

@ -0,0 +1,10 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class GetFeedbackRequest
{
[DataMember(EmitDefaultValue = false)]
public ApplicationStateModel ApplicationState { get; set; }
}
}

View file

@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class GetFeedbackResponse
{
[DataMember(EmitDefaultValue = false)]
public List<Message> messages { get; set; }
}
}

View file

@ -0,0 +1,16 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class ImageModel
{
[DataMember(EmitDefaultValue = false)]
public string Name { get; set; }
[DataMember(EmitDefaultValue = false)]
public string Url { get; set; }
[DataMember(EmitDefaultValue = false)]
public string Base64Image { get; set; }
[DataMember(EmitDefaultValue = false)]
public string Svg { get; set; }
}
}

View file

@ -0,0 +1,19 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class Margin
{
[DataMember(EmitDefaultValue = false)]
public double? left { get; set; }
[DataMember(EmitDefaultValue = false)]
public double? bottom { get; set; }
[DataMember(EmitDefaultValue = false)]
public double? top { get; set; }
[DataMember(EmitDefaultValue = false)]
public double? right { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace SVGLDLibs.Models
{
public class Message
{
public string text { get; set; }
public MessageType type { get; set; }
}
}

View file

@ -0,0 +1,10 @@
namespace SVGLDLibs.Models
{
public enum MessageType
{
Normal,
Success,
Warning,
Error
}
}

View file

@ -0,0 +1,12 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public enum Orientation
{
[EnumMember]
Horizontal,
[EnumMember]
Vertical
}
}

View file

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class Pattern
{
public Pattern()
{
children = new List<string>();
}
[DataMember(EmitDefaultValue = false)]
public string id { get; set; }
[DataMember(EmitDefaultValue = false)]
public string text { get; set; }
[DataMember(EmitDefaultValue = false)]
public string wrapper { get; set; }
[DataMember(EmitDefaultValue = true, IsRequired = true)]
public List<string> children { get; set; }
}
}

View file

@ -0,0 +1,12 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class PointModel
{
[DataMember(EmitDefaultValue = false)]
public string X { get; set; }
[DataMember(EmitDefaultValue = false)]
public string Y { get; set; }
}
}

View file

@ -0,0 +1,16 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public enum Position
{
[EnumMember]
Left,
[EnumMember]
Down,
[EnumMember]
Up,
[EnumMember]
Right
}
}

View file

@ -0,0 +1,26 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public enum PositionReferenceEnumModel
{
[EnumMember]
TopLeft,
[EnumMember]
TopCenter,
[EnumMember]
TopRight,
[EnumMember]
CenterLeft,
[EnumMember]
CenterCenter,
[EnumMember]
CenterRight,
[EnumMember]
BottomLeft,
[EnumMember]
BottomCenter,
[EnumMember]
BottomRight
}
}

View file

@ -0,0 +1,22 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class SetContainerListRequest
{
[DataMember(EmitDefaultValue = false)]
public ActionContainerModel Action { get; set; }
[DataMember(EmitDefaultValue = false)]
public ContainerModel Container { get; set; }
[DataMember(EmitDefaultValue = false)]
public ContainerModel PreviousContainer { get; set; }
[DataMember(EmitDefaultValue = false)]
public ContainerModel NextContainer { get; set; }
[DataMember(EmitDefaultValue = false)]
public ApplicationStateModel ApplicationState { get; set; }
}
}

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class SetContainerListResponse
{
[DataMember(EmitDefaultValue = false)]
public List<AvailableContainerModel> Containers { get; set; }
[DataMember(EmitDefaultValue = false)]
public AddingBehaviorEnumModel AddingBehavior { get; set; }
}
}

View file

@ -0,0 +1,16 @@
using System.Runtime.Serialization;
namespace SVGLDLibs.Models
{
public class SymbolModel : AvailableSymbolModel
{
[DataMember(EmitDefaultValue = false)]
public string Id { get; set; }
[DataMember(EmitDefaultValue = false)]
public PointModel Point { get; set; }
[DataMember(EmitDefaultValue = false)]
public bool IsLinkedToContainer { get; set; }
[DataMember(EmitDefaultValue = false)]
public string LinkedContainerId { get; set; }
}
}

View file

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,155 @@
using Microsoft.AspNetCore.Mvc;
using SVGLDLibs.Models;
namespace SVGLDWebAPI.Controllers;
[ApiController]
[Route("[controller]/[Action]")]
public class SVGLDController : ControllerBase
{
[HttpPost(Name = nameof(SVGLDLibs.Models.ActionContainerModel))]
public bool ActionContainerModel(ActionContainerModel model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.AddingBehaviorEnumModel))]
public bool AddingBehaviorEnumModel(AddingBehaviorEnumModel model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.ApplicationStateModel))]
public bool ApplicationStateModel(ApplicationStateModel model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.AvailableContainerModel))]
public bool AvailableContainerModel(AvailableContainerModel model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.AvailableSymbolModel))]
public bool AvailableSymbolModel(AvailableSymbolModel model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.Category))]
public bool Category(Category model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.ConfigurationResponseModel))]
public bool ConfigurationResponseModel(ConfigurationResponseModel model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.ContainerModel))]
public bool ContainerModel(ContainerModel model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.ContainerProperties))]
public bool ContainerProperties(ContainerProperties model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.CSSStyle))]
public bool CSSStyle(CSSStyle model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.GetFeedbackRequest))]
public bool GetFeedbackRequest(GetFeedbackRequest model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.GetFeedbackResponse))]
public bool GetFeedbackResponse(GetFeedbackResponse model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.ImageModel))]
public bool ImageModel(ImageModel model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.Margin))]
public bool Margin(Margin model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.Message))]
public bool Message(Message model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.MessageType))]
public bool MessageType(MessageType model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.Orientation))]
public bool Orientation(Orientation model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.Pattern))]
public bool Pattern(Pattern model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.PointModel))]
public bool PointModel(PointModel model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.Position))]
public bool Position(Position model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.PositionReferenceEnumModel))]
public bool PositionReferenceEnumModel(PositionReferenceEnumModel model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.SetContainerListRequest))]
public bool SetContainerListRequest(SetContainerListRequest model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.SetContainerListResponse))]
public bool SetContainerListResponse(SetContainerListResponse model)
{
return true;
}
[HttpPost(Name = nameof(SVGLDLibs.Models.SymbolModel))]
public bool SymbolModel(SymbolModel model)
{
return true;
}
}

View file

@ -0,0 +1,37 @@
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
const string allowOrigin = "allowOrigin";
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddCors(options =>
{
options.AddPolicy(name: allowOrigin,
policy =>
{
policy.WithOrigins("http://localhost:3000");
});
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors(allowOrigin);
// app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View file

@ -0,0 +1,31 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:2891",
"sslPort": 44305
}
},
"profiles": {
"SVGLDWebAPI": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7280;http://localhost:5209",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View file

@ -0,0 +1,15 @@
# SVGLDWebAPI
This project tests the serialization of C# models of SVGLayoutDesigner
by creating an entry point for each model.
This project must be used in pair with the api.test.tsx from SVGLayoutDesigner nodejs project.
api.test.tsx will serialize each of its models accordingly to its types
and POST to each corresponding entry point.
When a test is succeeding `true` will be return.
When a test is failing `400 Bad Request` will be returned.
This project does not test any logic in the models.

View file

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SVGLDLibs\SVGLDLibs.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View file

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View file

@ -10,6 +10,8 @@
"test": "vitest", "test": "vitest",
"test:ui": "vitest --ui", "test:ui": "vitest --ui",
"test:nowatch": "vitest run", "test:nowatch": "vitest run",
"test:full": "(node ./test-server/http.js &) && (dotnet run --project=./csharp/SVGLDLibs/SVGLDWebAPI/SVGLDWebAPI.csproj &) && sleep 3 && vitest ; pkill dotnet ; pkill node",
"test:full:nowatch": "(node ./test-server/http.js &) && (dotnet run --project=./csharp/SVGLDLibs/SVGLDWebAPI/SVGLDWebAPI.csproj &) && sleep 3 && vitest run ; pkill dotnet ; pkill node",
"coverage": "vitest run coverage" "coverage": "vitest run coverage"
}, },
"dependencies": { "dependencies": {

View file

@ -1,7 +1,27 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { AddMethod } from '../../Enums/AddMethod';
import { Orientation } from '../../Enums/Orientation';
import { Position } from '../../Enums/Position';
import { PositionReference } from '../../Enums/PositionReference';
import { IAction } from '../../Interfaces/IAction';
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
import { ICategory } from '../../Interfaces/ICategory';
import { IConfiguration } from '../../Interfaces/IConfiguration';
import { IContainerModel, ContainerModel } from '../../Interfaces/IContainerModel';
import { IEditorState } from '../../Interfaces/IEditorState';
import { IHistoryState } from '../../Interfaces/IHistoryState';
import { IPattern } from '../../Interfaces/IPattern';
import { DEFAULT_MAINCONTAINER_PROPS, GetDefaultContainerProps } from '../../utils/default';
import { FetchConfiguration } from './api'; import { FetchConfiguration } from './api';
describe.concurrent('API test', () => { const CSHARP_WEB_API_BASE_URL = 'http://localhost:5209/';
const CHARP_WEB_API_RESOURCE_URL = 'SVGLD';
const CSHARP_WEB_API_URL = CSHARP_WEB_API_BASE_URL + CHARP_WEB_API_RESOURCE_URL + '/';
// TODO: Migrate this test to SVGLDWebAPI rather than using test-server/
describe.concurrent('Test server test', () => {
it('Load environment', () => { it('Load environment', () => {
const url = import.meta.env.VITE_API_FETCH_URL; const url = import.meta.env.VITE_API_FETCH_URL;
expect(url).toBe('http://localhost:5000'); expect(url).toBe('http://localhost:5000');
@ -16,3 +36,258 @@ describe.concurrent('API test', () => {
expect(configuration.AvailableSymbols.length).toBeGreaterThan(-1); expect(configuration.AvailableSymbols.length).toBeGreaterThan(-1);
}); });
}); });
async function Post2API(resource: string, body: string, method = 'POST'): Promise<boolean> {
return await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, CSHARP_WEB_API_URL + resource, true);
xhr.onreadystatechange = () => { // Call a function when the state changes.
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
resolve(xhr.response === 'true');
}
if (xhr.status === 400) {
reject(xhr);
}
};
xhr.onerror = () => {
reject(xhr);
};
xhr.setRequestHeader('Content-type', 'application/json');
xhr.send(body);
});
}
/**
* Serialization tests for type compatibility of csharp classes
* See csharp/SVGLDWebAPI/README.md
*/
describe.concurrent('Models test suite', () => {
it('ActionContainerModel', async() => {
const model: IAction = {
Id: 'string',
CustomLogo: {
Name: 'string',
Url: 'string',
Base64Image: 'string',
Svg: 'string'
},
Label: 'string',
Description: 'string',
Action: 'string',
AddingBehavior: 0
};
const res = await Post2API('ActionContainerModel', JSON.stringify(model));
expect(res).toBe(true);
});
it('AddingBehaviorEnumModel', async() => {
let model: AddMethod = AddMethod.Append;
const res0 = await Post2API('AddingBehaviorEnumModel', JSON.stringify(model));
model = AddMethod.Insert;
const res1 = await Post2API('AddingBehaviorEnumModel', JSON.stringify(model));
model = AddMethod.Replace;
const res2 = await Post2API('AddingBehaviorEnumModel', JSON.stringify(model));
model = AddMethod.ReplaceParent;
const res3 = await Post2API('AddingBehaviorEnumModel', JSON.stringify(model));
model = 69;
const res4 = await Post2API('AddingBehaviorEnumModel', JSON.stringify(model));
expect(res0).toBe(true);
expect(res1).toBe(true);
expect(res2).toBe(true);
expect(res3).toBe(true);
expect(res4).toBe(true);
});
const mainContainer = new ContainerModel(
null,
DEFAULT_MAINCONTAINER_PROPS
);
const historyState: IHistoryState = {
lastAction: 'string',
mainContainer,
selectedContainerId: '3',
typeCounters: {
main: 1
},
symbols: new Map(),
selectedSymbolId: ''
};
it('ApplicationStateModel', async() => {
const res = await Post2API('ApplicationStateModel', JSON.stringify(historyState));
expect(res).toBe(true);
});
const availableContainerModel: IAvailableContainer = {
Type: 'string',
DisplayedText: 'string',
Category: 'string',
Orientation: 0,
X: 0,
Y: 0,
Width: 0,
Height: 0,
MinWidth: 0,
MaxWidth: 0,
MinHeight: 0,
MaxHeight: 0,
Margin: {
left: 0,
bottom: 0,
top: 0,
right: 0
},
IsAnchor: true,
IsFlex: true,
Pattern: 'string',
AddMethod: 0,
DefaultChildType: 'string',
PositionReference: 0,
HideChildrenInTreeview: true,
ShowSelfDimensions: [
Position.Down,
Position.Left,
Position.Right,
Position.Up
],
ShowChildrenDimensions: [
Position.Down,
Position.Left,
Position.Right,
Position.Up
],
MarkPosition: [
Orientation.Horizontal,
Orientation.Vertical
],
ShowDimensionWithMarks: [
Position.Down,
Position.Left,
Position.Right,
Position.Up
],
IsHidden: true,
Blacklist: [
'string'
],
Whitelist: [
'string'
],
Actions: [
{
Id: 'string',
CustomLogo: {
Name: 'string',
Url: 'string',
Base64Image: 'string',
Svg: 'string'
},
Label: 'string',
Description: 'string',
Action: 'string',
AddingBehavior: 0
}
],
CustomSVG: 'string',
Style: {},
UserData: {
additionalProp1: 'string',
additionalProp2: 'string',
additionalProp3: 'string'
}
};
it('AvailableContainerModel', async() => {
const res = await Post2API('ApplicationStateModel', JSON.stringify(availableContainerModel));
expect(res).toBe(true);
});
const availableSymbolModel: IAvailableSymbol = {
Name: 'string',
Image: {
Name: 'string',
Url: 'string',
Base64Image: 'string',
Svg: 'string'
},
PositionReference: PositionReference.BottomCenter,
Width: 0,
Height: 0
};
it('AvailableSymbolModel', async() => {
const res = await Post2API('AvailableSymbolModel', JSON.stringify(availableSymbolModel));
expect(res).toBe(true);
});
const category: ICategory = {
Type: 'string',
DisplayedText: 'string'
};
it('Category', async() => {
const res = await Post2API('Category', JSON.stringify(category));
expect(res).toBe(true);
});
const pattern: IPattern = {
children: ['string'],
id: 'string',
text: 'string',
wrapper: 'string'
};
it('ConfigurationResponseModel', async() => {
const model: IConfiguration = {
AvailableContainers: [
availableContainerModel
],
AvailableSymbols: [
availableSymbolModel
],
Categories: [
category
],
MainContainer: availableContainerModel,
Patterns: [pattern]
};
const res = await Post2API('ConfigurationResponseModel', JSON.stringify(model));
expect(res).toBe(true);
});
const containerProperties = GetDefaultContainerProps(
'container',
0,
null,
0, 0, 0, 0, availableContainerModel);
it('ContainerModel', async() => {
const model: IContainerModel = {
children: [],
parent: null,
properties: containerProperties,
userData: {}
};
const res = await Post2API('ContainerModel', JSON.stringify(model));
expect(res).toBe(true);
});
it('ContainerProperties', async() => {
const res = await Post2API('ContainerModel', JSON.stringify(containerProperties));
expect(res).toBe(true);
});
it('CSSStyle', async() => {
const model: React.CSSProperties = {
strokeWidth: '0',
fillOpacity: '0',
stroke: 'black',
fill: 'black'
};
const res = await Post2API('CSSStyle', JSON.stringify(model));
expect(res).toBe(true);
});
});

View file

@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ES2021",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"], "lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false, "allowJs": false,