Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PaYaml V3 updates #679

Merged
merged 1 commit into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions schemas/pa-yaml/v3.0/pa.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ definitions:
type: object
additionalProperties: false
properties:
Variant: { $ref: "#/definitions/Control-variant-name" }
Properties: { $ref: "#/definitions/Properties-formula-map" }
Groups: { $ref: "#/definitions/Groups-of-controls" }
Children: { $ref: "#/definitions/Children-Control-instance-sequence" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,23 @@ public static NamedObjectAssertions<TValue> Should<TValue>(this NamedObject<TVal
/// Contains a number of methods to assert that a <see cref="NamedObjectMapping{TValue}"/> is in the expected state.
/// </summary>
[DebuggerNonUserCode]
public class NamedObjectAssertions<TValue>
: NamedObjectAssertions<NamedObject<TValue>?, string, TValue, NamedObjectAssertions<TValue>>
public class NamedObjectAssertions<TValue>(NamedObject<TValue>? actualValue)
: NamedObjectAssertions<NamedObject<TValue>?, string, TValue, NamedObjectAssertions<TValue>>(actualValue)
where TValue : notnull
{
public NamedObjectAssertions(NamedObject<TValue>? actualValue)
: base(actualValue)
{
}
}

/// <summary>
/// Contains a number of methods to assert that a <see cref="NamedObjectMapping{TValue}"/> is in the expected state.
/// </summary>
[DebuggerNonUserCode]
public class NamedObjectAssertions<TNamedObject, TName, TValue, TAssertions>
: ReferenceTypeAssertions<TNamedObject, TAssertions>
public class NamedObjectAssertions<TNamedObject, TName, TValue, TAssertions>(TNamedObject actualValue)
: ReferenceTypeAssertions<TNamedObject, TAssertions>(actualValue)
where TNamedObject : INamedObject<TName, TValue>?
where TName : notnull
where TValue : notnull
where TAssertions : NamedObjectAssertions<TNamedObject, TName, TValue, TAssertions>
{
public NamedObjectAssertions(TNamedObject actualValue)
: base(actualValue)
{
}

protected override string Identifier => "NamedObject";

public AndConstraint<TAssertions> HaveValueEqual(TValue expected, string because = "", params object[] becauseArgs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,22 @@ public static NamedObjectCollectionAssertions<TValue> Should<TValue>(this IReadO
/// Contains a number of methods to assert that a <see cref="NamedObjectMapping{TValue}"/> is in the expected state.
/// </summary>
[DebuggerNonUserCode]
public class NamedObjectCollectionAssertions<TValue>
: NamedObjectCollectionAssertions<IReadOnlyNamedObjectCollection<TValue>?, TValue, NamedObjectCollectionAssertions<TValue>>
public class NamedObjectCollectionAssertions<TValue>(IReadOnlyNamedObjectCollection<TValue>? actualValue)
: NamedObjectCollectionAssertions<IReadOnlyNamedObjectCollection<TValue>?, TValue, NamedObjectCollectionAssertions<TValue>>(actualValue)
where TValue : notnull
{
public NamedObjectCollectionAssertions(IReadOnlyNamedObjectCollection<TValue>? actualValue)
: base(actualValue)
{
}
}

/// <summary>
/// Contains a number of methods to assert that a <see cref="NamedObjectMapping{TValue}"/> is in the expected state.
/// </summary>
[DebuggerNonUserCode]
public class NamedObjectCollectionAssertions<TCollection, TValue, TAssertions>
: GenericCollectionAssertions<TCollection, NamedObject<TValue>, TAssertions>
public class NamedObjectCollectionAssertions<TCollection, TValue, TAssertions>(TCollection actualValue)
: GenericCollectionAssertions<TCollection, NamedObject<TValue>, TAssertions>(actualValue)
where TCollection : IReadOnlyNamedObjectCollection<TValue>?
where TValue : notnull
where TAssertions : NamedObjectCollectionAssertions<TCollection, TValue, TAssertions>
{
public NamedObjectCollectionAssertions(TCollection actualValue)
: base(actualValue)
{
}

public AndConstraint<TAssertions> ContainNames(params string[] expected)
{
return ContainNames(expected, string.Empty);
Expand Down Expand Up @@ -98,16 +89,12 @@ public WhoseNamedObjectConstraint<TCollection, TValue, TAssertions> ContainName(
}

[DebuggerNonUserCode]
public class WhoseNamedObjectConstraint<TCollection, TValue, TAssertions> : AndConstraint<TAssertions>
public class WhoseNamedObjectConstraint<TCollection, TValue, TAssertions>(TAssertions parentConstraint, NamedObject<TValue> namedObject)
: AndConstraint<TAssertions>(parentConstraint)
where TCollection : IReadOnlyNamedObjectCollection<TValue>?
where TValue : notnull
where TAssertions : NamedObjectCollectionAssertions<TCollection, TValue, TAssertions>
{
public WhoseNamedObjectConstraint(TAssertions parentConstraint, NamedObject<TValue> namedObject) : base(parentConstraint)
{
WhoseNamedObject = namedObject;
}

public NamedObject<TValue> WhoseNamedObject { get; }
public NamedObject<TValue> WhoseNamedObject { get; } = namedObject;
public TValue WhoseValue => WhoseNamedObject.Value;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using FluentAssertions.Primitives;
using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;

namespace Persistence.Tests.Extensions;

[DebuggerNonUserCode]
public static class PaYamlLocationAssertionExtensions
{
public static PaYamlLocationAssertions Should(this PaYamlLocation? actualValue)
{
return new(actualValue);
}
}

[DebuggerNonUserCode]
public class PaYamlLocationAssertions(PaYamlLocation? actualValue)
: ObjectAssertions<PaYamlLocation?, PaYamlLocationAssertions>(actualValue)
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ public static void ShouldNotBeNull<T>([NotNull] this T? value)
}
}

/// <summary>
/// Asserts that the specified object does not define a member with the specified name.
/// This is useful to document tests which are written given that some future expected member is not yet defined.
/// </summary>
public static AndConstraint<TAssertions> NotDefineMember<TSubject, TAssertions>(this ObjectAssertions<TSubject, TAssertions> assertions, string memberName, string because = "", params object[] becauseArgs)
where TAssertions : ObjectAssertions<TSubject, TAssertions>
where TSubject : class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ public void DescendantControlInstancesSingleLevel()
{
var screen = new NamedObject<ScreenInstance>("Screen1", new()
{
Children =
{
Children = [
new("Ctrl0", new("GroupContainer")),
new("Ctrl1", new("GroupContainer")),
new("Ctrl2", new("GroupContainer")),
},
],
});

screen.DescendantControlInstances().SelectNames().Should().Equal(new[]
Expand All @@ -35,22 +34,17 @@ public void DescendantControlInstances1AtEachLevel()
{
var screen = new NamedObject<ScreenInstance>("Screen1", new()
{
Children =
{
Children = [
new("Ctrl0", new("GroupContainer")
{
Children =
{
Children = [
new("Ctrl0.0", new("GroupContainer")
{
Children =
{
new("Ctrl0.0.0", new("GroupContainer")),
}
Children = [new("Ctrl0.0.0", new("GroupContainer"))]
}),
}
]
}),
},
],
});

screen.DescendantControlInstances().SelectNames().Should().Equal(new[]
Expand All @@ -66,41 +60,36 @@ public void DescendantControlInstancesMultiLevelTest()
{
var screen = new NamedObject<ScreenInstance>("Screen1", new()
{
Children =
{
Children = [
new("Ctrl0", new("GroupContainer")
{
Children =
{
Children = [
new("Ctrl0.0", new("Label")),
new("Ctrl0.1", new("Label")),
},
],
}),
new("Ctrl1", new("GroupContainer")
{
Children =
{
Children = [
new("Ctrl1.0", new("Label")),
new("Ctrl1.1", new("GroupContainer")
{
Children =
{
Children = [
new("Ctrl1.1.0", new("Label")),
new("Ctrl1.1.1", new("Label")),
},
],
}),
new("Ctrl1.2", new("Label")),
},
],
}),
new("Ctrl2", new("GroupContainer")
{
Children =
{
Children = [
new("Ctrl2.0", new("Label")),
new("Ctrl2.1", new("Label")),
},
],
}),
},
],
});

screen.DescendantControlInstances().SelectNames().Should().Equal(new[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ namespace Persistence.Tests.PaYaml.Serialization;
[TestClass]
public class NamedObjectSequenceSerializationTests : SerializationTestBase
{
protected override void ConfigureYamlDotNetDeserializer(DeserializerBuilder builder, PaYamlSerializerOptions options, SerializationContext serializationContext)
protected override void ConfigureYamlDotNetDeserializer(DeserializerBuilder builder, PaYamlSerializationContext context)
{
base.ConfigureYamlDotNetDeserializer(builder, options, serializationContext);
builder.WithTypeConverter(new NamedObjectYamlConverter<string>(serializationContext));
base.ConfigureYamlDotNetDeserializer(builder, context);
builder.WithTypeConverter(new NamedObjectYamlConverter<string>(context));
}

protected override void ConfigureYamlDotNetSerializer(SerializerBuilder builder, PaYamlSerializerOptions options, SerializationContext serializationContext)
protected override void ConfigureYamlDotNetSerializer(SerializerBuilder builder, PaYamlSerializationContext context)
{
base.ConfigureYamlDotNetSerializer(builder, options, serializationContext);
builder.WithTypeConverter(new NamedObjectYamlConverter<string>(serializationContext));
base.ConfigureYamlDotNetSerializer(builder, context);
builder.WithTypeConverter(new NamedObjectYamlConverter<string>(context));
}

[TestMethod]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ public PFxExpressionYamlConverterTests()
};
}

protected override void ConfigureYamlDotNetDeserializer(DeserializerBuilder builder, PaYamlSerializerOptions options, SerializationContext serializationContext)
protected override void ConfigureYamlDotNetDeserializer(DeserializerBuilder builder, PaYamlSerializationContext context)
{
base.ConfigureYamlDotNetDeserializer(builder, options, serializationContext);
builder.WithTypeConverter(new PFxExpressionYamlConverter(options.PFxExpressionYamlFormatting));
base.ConfigureYamlDotNetDeserializer(builder, context);
builder.WithTypeConverter(new PFxExpressionYamlConverter(context.Options.PFxExpressionYamlFormatting));
}

protected override void ConfigureYamlDotNetSerializer(SerializerBuilder builder, PaYamlSerializerOptions options, SerializationContext serializationContext)
protected override void ConfigureYamlDotNetSerializer(SerializerBuilder builder, PaYamlSerializationContext context)
{
base.ConfigureYamlDotNetSerializer(builder, options, serializationContext);
builder.WithTypeConverter(new PFxExpressionYamlConverter(options.PFxExpressionYamlFormatting));
base.ConfigureYamlDotNetSerializer(builder, context);
builder.WithTypeConverter(new PFxExpressionYamlConverter(context.Options.PFxExpressionYamlFormatting));
}

[TestMethod]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,30 @@ namespace Persistence.Tests.PaYaml.Serialization;
[TestClass]
public class PaYamlSerializerTests : VSTestBase
{
[TestMethod]
public void DeserializeNamedObjectSetsLocationInfo()
{
// Since App.Properties is a NamedObjectMapping, the location info should be set on the NamedObject
var paModule = PaYamlSerializer.Deserialize<PaModule>("""
App:
Properties:
Foo: =true
Bar: ="hello world"
""");
paModule.ShouldNotBeNull();
paModule.App.ShouldNotBeNull();
paModule.App.Properties.ShouldNotBeNull();
paModule.App.Properties.Should().ContainName("Foo").WhoseNamedObject.Start.Should().Be(new(3, 9));
paModule.App.Properties.Should().ContainName("Bar").WhoseNamedObject.Start.Should().Be(new(4, 9));
}

#region Deserialize Examples

[TestMethod]
[DataRow(@"_TestData/SchemaV3_0/Examples/Src/App.pa.yaml", 5)]
public void DeserializeExamplePaYamlApp(string path, int expectedAppPropertiesCount)
{
var paFileRoot = YamlSerializer.Deserialize<PaModule>(File.ReadAllText(path));
var paFileRoot = PaYamlSerializer.Deserialize<PaModule>(File.ReadAllText(path));
paFileRoot.ShouldNotBeNull();

// Top level properties
Expand All @@ -34,7 +51,7 @@ public void DeserializeExamplePaYamlApp(string path, int expectedAppPropertiesCo
[DataRow(@"_TestData/SchemaV3_0/Examples/Src/Screens/ComponentsScreen4.pa.yaml", 0, 6, 6, 0, 0)]
public void DeserializeExamplePaYamlScreen(string path, int expectedScreenPropertiesCount, int expectedScreenChildrenCount, int expectedDescendantsCount, int expectedScreenGroupsCount, int expectedTotalGroupsCount)
{
var paFileRoot = YamlSerializer.Deserialize<PaModule>(File.ReadAllText(path));
var paFileRoot = PaYamlSerializer.Deserialize<PaModule>(File.ReadAllText(path));
paFileRoot.ShouldNotBeNull();

// Top level properties
Expand All @@ -45,19 +62,33 @@ public void DeserializeExamplePaYamlScreen(string path, int expectedScreenProper
// Check screen counts
paFileRoot.Screens.Should().HaveCount(1);
var screen = paFileRoot.Screens.First().Value;
screen.Properties.Should().HaveCount(expectedScreenPropertiesCount);
screen.Children.Should().HaveCount(expectedScreenChildrenCount);
screen.DescendantControlInstances().Should().HaveCount(expectedDescendantsCount);

screen.Groups.Should().HaveCount(expectedScreenGroupsCount);
screen.DescendantControlInstances().SelectMany(nc => nc.Value.Groups).Should().HaveCount(expectedTotalGroupsCount - expectedScreenGroupsCount);
if (expectedScreenPropertiesCount == 0)
screen.Properties.Should().BeNull();
else
screen.Properties.Should().HaveCount(expectedScreenPropertiesCount);

if (expectedScreenChildrenCount == 0)
screen.Children.Should().BeNull();
else
screen.Children.Should().HaveCount(expectedScreenChildrenCount);

if (expectedDescendantsCount == 0)
screen.Properties.Should().BeNull();
else
screen.DescendantControlInstances().Should().HaveCount(expectedDescendantsCount);

if (expectedScreenGroupsCount == 0)
screen.Properties.Should().BeNull();
else
screen.Groups.Should().HaveCount(expectedScreenGroupsCount);
screen.DescendantControlInstances().SelectMany(nc => nc.Value.Groups ?? []).Should().HaveCount(expectedTotalGroupsCount - expectedScreenGroupsCount);
}

[TestMethod]
[DataRow(@"_TestData/SchemaV3_0/Examples/Src/Components/MyHeaderComponent.pa.yaml", 9, 6, 1)]
public void DeserializeExamplePaYamlComponentDefinition(string path, int expectedCustomPropertiesCount, int expectedPropertiesCount, int expectedChildrenCount)
{
var paFileRoot = YamlSerializer.Deserialize<PaModule>(File.ReadAllText(path));
var paFileRoot = PaYamlSerializer.Deserialize<PaModule>(File.ReadAllText(path));
paFileRoot.ShouldNotBeNull();

// Top level properties
Expand All @@ -77,7 +108,7 @@ public void DeserializeExamplePaYamlComponentDefinition(string path, int expecte
public void DeserializeExamplePaYamlSingleFileApp()
{
var path = @"_TestData/SchemaV3_0/Examples/Single-File-App.pa.yaml";
var paFileRoot = YamlSerializer.Deserialize<PaModule>(File.ReadAllText(path));
var paFileRoot = PaYamlSerializer.Deserialize<PaModule>(File.ReadAllText(path));
paFileRoot.ShouldNotBeNull();

// Top level properties
Expand Down Expand Up @@ -112,10 +143,10 @@ public void DeserializeExamplePaYamlSingleFileApp()
public void RoundTripFromYaml(string path)
{
var originalYaml = File.ReadAllText(path);
var paFileRoot = YamlSerializer.Deserialize<PaModule>(originalYaml);
var paFileRoot = PaYamlSerializer.Deserialize<PaModule>(originalYaml);
paFileRoot.ShouldNotBeNull();

var roundTrippedYaml = YamlSerializer.Serialize(paFileRoot);
var roundTrippedYaml = PaYamlSerializer.Serialize(paFileRoot);
TestContext.WriteTextWithLineNumbers(roundTrippedYaml, "roundTrippedYaml:");
roundTrippedYaml.Should().BeYamlEquivalentTo(originalYaml);
}
Expand Down
Loading
Loading