Skip to content

Commit

Permalink
Added Component Instance (#636)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrew Petrochuk (from Dev Box) <[email protected]>
  • Loading branch information
petrochuk and anpetroc authored Apr 15, 2024
1 parent bd3b001 commit 4b38acd
Show file tree
Hide file tree
Showing 25 changed files with 422 additions and 414 deletions.
11 changes: 5 additions & 6 deletions src/Persistence.Tests/Templates/ControlFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,17 @@ public void CreateFromTemplateName_Should_CreateFirstClassTypes()
}

[TestMethod]
[DataRow("Component", "http://microsoft.com/appmagic/Component")]
[DataRow("CommandComponent", "http://microsoft.com/appmagic/CommandComponent")]
public void CreateComponent_ShouldCreateValidInstance(string componentType, string expectedTemplateId)
[DataRow("Component", "MyComponent1", "http://microsoft.com/appmagic/Component")]
public void CreateComponent_ShouldCreateValidInstance(string componentType, string componentName, string expectedTemplateId)
{
var sut = new ControlFactory(ControlTemplateStore);

var result = sut.Create("MyComponent1", componentType);
var result = sut.Create(componentName, componentType);
result.Should().NotBeNull().And.BeAssignableTo<ComponentDefinition>();
result.Name.Should().Be("MyComponent1");
result.Name.Should().Be(componentName);
result.Template.Should().NotBeNull();
result.Template.Id.Should().Be(expectedTemplateId);
result.Template.Name.Should().Be(componentType);
result.Template.Name.Should().Be(componentName);
result.Properties.Should().NotBeNull().And.BeEmpty();
result.Children.Should().BeNull();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Persistence.Tests/Yaml/DeserializeComponentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void Deserialize_Component_Should_Succeed(string path, bool isControlIden
component.Children!.Count.Should().Be(childrenCount);
component.Properties!.Count.Should().Be(propertiesCount);
component.Template.Should().NotBeNull();
component.Template!.Name.Should().Be("Component");
component.Template!.Name.Should().Be("Component1");
component.CustomProperties.Count.Should().Be(customPropertiesCount);
component.CustomProperties[0].Name.Should().Be(firstCustomProperyName);
component.CustomProperties[0].Parameters.Count.Should().Be(firstCustomProperyParametersCount);
Expand Down
2 changes: 1 addition & 1 deletion src/Persistence.Tests/Yaml/DeserializerInvalidTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public void Deserialize_ShouldFailWhenExpectingDifferentType(bool isControlIdent
// Assert
act.Should().Throw<YamlException>()
.WithInnerException<NotSupportedException>()
.WithMessage("Cannot covert Microsoft.PowerPlatform.PowerApps.Persistence.Models.Screen to BuiltInControl");
.WithMessage("Cannot covert Screen to BuiltInControl");
}

[TestMethod]
Expand Down
11 changes: 5 additions & 6 deletions src/Persistence.Tests/Yaml/DeserializerValidTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -349,14 +349,13 @@ public void Deserialize_Strings()
}

[TestMethod]
[DataRow(@"_TestData/ValidYaml{0}/Component.pa.yaml", true, "MyCustomComponent", "Component", "http://microsoft.com/appmagic/Component", "lorem ipsum", true)]
[DataRow(@"_TestData/ValidYaml{0}/Component.pa.yaml", false, "MyCustomComponent", "Component", "http://microsoft.com/appmagic/Component", "lorem ipsum", true)]
[DataRow(@"_TestData/ValidYaml{0}/CommandComponent.pa.yaml", true, "MyCustomCommandComponent", "CommandComponent", "http://microsoft.com/appmagic/CommandComponent", "lorem ipsum", true)]
[DataRow(@"_TestData/ValidYaml{0}/CommandComponent.pa.yaml", false, "MyCustomCommandComponent", "CommandComponent", "http://microsoft.com/appmagic/CommandComponent", "lorem ipsum", true)]
[DataRow(@"_TestData/ValidYaml{0}/Component.pa.yaml", true, "MyCustomComponent", "http://microsoft.com/appmagic/Component", "lorem ipsum", true)]
[DataRow(@"_TestData/ValidYaml{0}/Component.pa.yaml", false, "MyCustomComponent", "http://microsoft.com/appmagic/Component", "lorem ipsum", true)]
[DataRow(@"_TestData/ValidYaml{0}/Components/CommandComponent.pa.yaml", true, "MyCustomCommandComponent", "http://microsoft.com/appmagic/CommandComponent", "lorem ipsum", true)]
[DataRow(@"_TestData/ValidYaml{0}/Components/CommandComponent.pa.yaml", false, "MyCustomCommandComponent", "http://microsoft.com/appmagic/CommandComponent", "lorem ipsum", true)]
public void Deserialize_Component_ShouldSucceed(
string path, bool isControlIdentifiers,
string expectedName,
string expectedTemplateName,
string expectedTemplateId,
string expectedDescription,
bool expectedAccessAppScope)
Expand All @@ -374,7 +373,7 @@ public void Deserialize_Component_ShouldSucceed(
component.Description.Should().Be(expectedDescription);
component.AccessAppScope.Should().Be(expectedAccessAppScope);
component.Template.Should().NotBeNull();
component.Template!.Name.Should().Be(expectedTemplateName);
component.Template!.Name.Should().Be(expectedName);
component.Template.Id.Should().Be(expectedTemplateId);
}

Expand Down
32 changes: 32 additions & 0 deletions src/Persistence.Tests/Yaml/RoundTripTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,36 @@ public void RoundTrip_ValidYaml(string path, bool isControlIdentifiers, Type roo
var expectedYaml = File.ReadAllText(GetTestFilePath(path, isControlIdentifiers)).NormalizeNewlines();
actualYaml.Should().Be(expectedYaml);
}

[TestMethod]
[DataRow(@"_TestData/ValidYaml{0}/Screen/with-component-instance.pa.yaml", true)]
public void Screen_With_Component_Instance(string path, bool isControlIdentifiers)
{
// Arrange
var deserializer = CreateDeserializer(isControlIdentifiers);
using var yamlStream = File.OpenRead(GetTestFilePath(path, isControlIdentifiers));
using var yamlReader = new StreamReader(yamlStream);

// Act I: Deserialize the yaml into an object.
var screen = deserializer.Deserialize<Control>(yamlReader) as Screen;
if (screen == null)
throw new InvalidOperationException("Failed to deserialize screen");

// Assert
screen.Children!.Count.Should().Be(1);
var customControl = screen.Children[0] as ComponentInstance;
if (customControl == null)
throw new InvalidOperationException("Failed to deserialize component instance");
customControl.Should().NotBeNull();
customControl.Name.Should().Be("This is custom component");
customControl.ComponentName.Should().Be("ComponentDefinition_1");

// Act II: Serialize the object back into yaml.
var serializer = CreateSerializer(isControlIdentifiers);
var actualYaml = serializer.SerializeControl(screen).NormalizeNewlines();

// Assert that the yaml is the same.
var expectedYaml = File.ReadAllText(GetTestFilePath(path, isControlIdentifiers)).NormalizeNewlines();
actualYaml.Should().Be(expectedYaml);
}
}
10 changes: 6 additions & 4 deletions src/Persistence.Tests/Yaml/ValidSerializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,13 +296,15 @@ public void Serialize_ShouldCreateValidYamlForComponentCustomProperties(CustomPr
}

[TestMethod]
[DataRow("lorem ipsum dolor", false, "Control: Component\nName: Component1\nDescription: lorem ipsum dolor\n")]
[DataRow("lorem ipsum dolor", true, "Control: Component\nName: Component1\nDescription: lorem ipsum dolor\nAccessAppScope: true\n")]
[DataRow("", true, "Control: Component\nName: Component1\nAccessAppScope: true\n")]
public void Serialize_ShouldCreateValidYamlForComponent(string description, bool accessAppScope, string expectedYaml)
[DataRow(ComponentType.Canvas, "lorem ipsum dolor", false, "Control: Component\nName: Component1\nDescription: lorem ipsum dolor\n")]
[DataRow(ComponentType.Canvas, "lorem ipsum dolor", true, "Control: Component\nName: Component1\nDescription: lorem ipsum dolor\nAccessAppScope: true\n")]
[DataRow(ComponentType.Canvas, "", true, "Control: Component\nName: Component1\nAccessAppScope: true\n")]
[DataRow(ComponentType.Command, "", true, "Control: Component\nName: Component1\nType: Command\nAccessAppScope: true\n")]
public void Serialize_ShouldCreateValidYamlForComponent(ComponentType componentType, string description, bool accessAppScope, string expectedYaml)
{
var component = (ComponentDefinition)ControlFactory.Create("Component1", "Component");
component.Should().NotBeNull();
component.Type = componentType;
component.Description = description;
component.AccessAppScope = accessAppScope;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
MyCustomCommandComponent:
Control: CommandComponent
Control: Component
Type: Command
Description: lorem ipsum
AccessAppScope: true
Properties:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Hello:
Control: Screen
Children:
- This is custom component:
Control: Component
ComponentName: ComponentDefinition_1
Properties:
X: =15
Y: =55
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Control: CommandComponent
Control: Component
Name: MyCustomCommandComponent
Type: Command
Description: lorem ipsum
AccessAppScope: true
Properties:
Expand Down
10 changes: 5 additions & 5 deletions src/Persistence/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ public static void AddPowerAppsPersistence(this IServiceCollection services, boo

private static void AddMinimalTemplates(ControlTemplateStore store)
{
store.Add(new() { Name = "hostControl", DisplayName = "host", Id = "http://microsoft.com/appmagic/hostcontrol" });
store.Add(new() { Name = "appinfo", DisplayName = "app", Id = "http://microsoft.com/appmagic/appinfo" });
store.Add(new() { Name = "screen", Id = "http://microsoft.com/appmagic/screen" });
store.Add(new() { Name = "component", Id = "http://microsoft.com/appmagic/Component" });
store.Add(new() { Name = "group", Id = "http://microsoft.com/appmagic/group" });
store.Add(new() { Name = "hostControl", DisplayName = "host", Id = BuiltInTemplates.Host.Id });
store.Add(new() { Name = "appinfo", DisplayName = "app", Id = BuiltInTemplates.App.Id });
store.Add(new() { Name = "screen", Id = BuiltInTemplates.Screen.Id });
store.Add(new() { Name = "component", Id = BuiltInTemplates.Component.Id });
store.Add(new() { Name = "group", Id = BuiltInTemplates.Group.Id });

// Gallery
store.Add(new()
Expand Down
95 changes: 3 additions & 92 deletions src/Persistence/Models/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ namespace Microsoft.PowerPlatform.PowerApps.Persistence.Models;
/// <summary>
/// Represents an Canvas App.
/// </summary>
[FirstClass(templateName: BuiltInTemplates.App)]
[FirstClass(templateName: "Appinfo")]
[YamlSerializable]
public record App : Control, IConvertible
public record App : Control
{
public App()
{
Expand All @@ -28,7 +28,7 @@ public App(string name, string variant, IControlTemplateStore controlTemplateSto
{
Name = name;
Variant = variant;
Template = controlTemplateStore.GetByName(BuiltInTemplates.App);
Template = controlTemplateStore.GetByName(BuiltInTemplates.App.Name);
}

[YamlIgnore]
Expand All @@ -49,93 +49,4 @@ internal override void AfterCreate(Dictionary<string, object?> controlDefinition
Screens = new List<Screen>();
}
}

#region IConvertible

TypeCode IConvertible.GetTypeCode()
{
return TypeCode.Object;
}

bool IConvertible.ToBoolean(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(bool)}");
}

byte IConvertible.ToByte(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(byte)}");
}

char IConvertible.ToChar(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(char)}");
}

DateTime IConvertible.ToDateTime(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(DateTime)}");
}

decimal IConvertible.ToDecimal(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(decimal)}");
}

double IConvertible.ToDouble(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(double)}");
}

short IConvertible.ToInt16(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(short)}");
}

int IConvertible.ToInt32(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(int)}");
}

long IConvertible.ToInt64(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(long)}");
}

sbyte IConvertible.ToSByte(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(sbyte)}");
}

float IConvertible.ToSingle(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(float)}");
}

string IConvertible.ToString(IFormatProvider? provider)
{
return Name;
}

object IConvertible.ToType(Type conversionType, IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {conversionType.Name}");
}

ushort IConvertible.ToUInt16(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(ushort)}");
}

uint IConvertible.ToUInt32(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(uint)}");
}

ulong IConvertible.ToUInt64(IFormatProvider? provider)
{
throw new NotSupportedException($"Cannot covert {typeof(App)} to {typeof(ulong)}");
}

#endregion
}
24 changes: 0 additions & 24 deletions src/Persistence/Models/CommandComponentDefinition.cs

This file was deleted.

Loading

0 comments on commit 4b38acd

Please sign in to comment.