309 lines
11 KiB
C#
309 lines
11 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
using Microsoft.AspNetCore.Blazor.RenderTree;
|
|
using Microsoft.AspNetCore.Blazor.Test.Helpers;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.Blazor.Test
|
|
{
|
|
public class ParameterCollectionAssignmentExtensionsTest
|
|
{
|
|
[Fact]
|
|
public void IncomingParameterMatchesAnnotatedPrivateProperty_SetsValue()
|
|
{
|
|
// Arrange
|
|
var someObject = new object();
|
|
var parameterCollection = new ParameterCollectionBuilder
|
|
{
|
|
{ nameof(HasInstanceProperties.IntProp), 123 },
|
|
{ nameof(HasInstanceProperties.StringProp), "Hello" },
|
|
{ HasInstanceProperties.ObjectPropName, someObject },
|
|
}.Build();
|
|
var target = new HasInstanceProperties();
|
|
|
|
// Act
|
|
parameterCollection.AssignToProperties(target);
|
|
|
|
// Assert
|
|
Assert.Equal(123, target.IntProp);
|
|
Assert.Equal("Hello", target.StringProp);
|
|
Assert.Same(someObject, target.ObjectPropCurrentValue);
|
|
}
|
|
|
|
[Fact]
|
|
public void IncomingParameterMatchesDeclaredParameterCaseInsensitively_SetsValue()
|
|
{
|
|
// Arrange
|
|
var parameterCollection = new ParameterCollectionBuilder
|
|
{
|
|
{ nameof(HasInstanceProperties.IntProp).ToLowerInvariant(), 123 }
|
|
}.Build();
|
|
var target = new HasInstanceProperties();
|
|
|
|
// Act
|
|
parameterCollection.AssignToProperties(target);
|
|
|
|
// Assert
|
|
Assert.Equal(123, target.IntProp);
|
|
}
|
|
|
|
[Fact]
|
|
public void IncomingParameterMatchesInheritedDeclaredParameter_SetsValue()
|
|
{
|
|
// Arrange
|
|
var parameterCollection = new ParameterCollectionBuilder
|
|
{
|
|
{ nameof(HasInheritedProperties.IntProp), 123 },
|
|
{ nameof(HasInheritedProperties.DerivedClassIntProp), 456 },
|
|
}.Build();
|
|
var target = new HasInheritedProperties();
|
|
|
|
// Act
|
|
parameterCollection.AssignToProperties(target);
|
|
|
|
// Assert
|
|
Assert.Equal(123, target.IntProp);
|
|
Assert.Equal(456, target.DerivedClassIntProp);
|
|
}
|
|
|
|
[Fact]
|
|
public void NoIncomingParameterMatchesDeclaredParameter_LeavesValueUnchanged()
|
|
{
|
|
// Arrange
|
|
var existingObjectValue = new object();
|
|
var target = new HasInstanceProperties
|
|
{
|
|
IntProp = 456,
|
|
StringProp = "Existing value",
|
|
ObjectPropCurrentValue = existingObjectValue
|
|
};
|
|
|
|
var parameterCollection = new ParameterCollectionBuilder().Build();
|
|
|
|
// Act
|
|
parameterCollection.AssignToProperties(target);
|
|
|
|
// Assert
|
|
Assert.Equal(456, target.IntProp);
|
|
Assert.Equal("Existing value", target.StringProp);
|
|
Assert.Same(existingObjectValue, target.ObjectPropCurrentValue);
|
|
}
|
|
|
|
[Fact]
|
|
public void IncomingParameterMatchesNoDeclaredParameter_Throws()
|
|
{
|
|
// Arrange
|
|
var target = new HasPropertyWithoutParameterAttribute();
|
|
var parameterCollection = new ParameterCollectionBuilder
|
|
{
|
|
{ "AnyOtherKey", 123 },
|
|
}.Build();
|
|
|
|
// Act
|
|
var ex = Assert.Throws<InvalidOperationException>(
|
|
() => parameterCollection.AssignToProperties(target));
|
|
|
|
// Assert
|
|
Assert.Equal(
|
|
$"Object of type '{typeof(HasPropertyWithoutParameterAttribute).FullName}' does not have a property " +
|
|
$"matching the name 'AnyOtherKey'.",
|
|
ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void IncomingParameterMatchesPropertyNotDeclaredAsParameter_Throws()
|
|
{
|
|
// Arrange
|
|
var target = new HasPropertyWithoutParameterAttribute();
|
|
var parameterCollection = new ParameterCollectionBuilder
|
|
{
|
|
{ nameof(HasPropertyWithoutParameterAttribute.IntProp), 123 },
|
|
}.Build();
|
|
|
|
// Act
|
|
var ex = Assert.Throws<InvalidOperationException>(
|
|
() => parameterCollection.AssignToProperties(target));
|
|
|
|
// Assert
|
|
Assert.Equal(default, target.IntProp);
|
|
Assert.Equal(
|
|
$"Object of type '{typeof(HasPropertyWithoutParameterAttribute).FullName}' has a property matching the name '{nameof(HasPropertyWithoutParameterAttribute.IntProp)}', " +
|
|
$"but it does not have [{nameof(ParameterAttribute)}] or [{nameof(CascadingParameterAttribute)}] applied.",
|
|
ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void IncomingParameterValueMismatchesDeclaredParameterType_Throws()
|
|
{
|
|
// Arrange
|
|
var someObject = new object();
|
|
var parameterCollection = new ParameterCollectionBuilder
|
|
{
|
|
{ nameof(HasInstanceProperties.IntProp), "string value" },
|
|
}.Build();
|
|
var target = new HasInstanceProperties();
|
|
|
|
// Act
|
|
var ex = Assert.Throws<InvalidOperationException>(
|
|
() => parameterCollection.AssignToProperties(target));
|
|
|
|
// Assert
|
|
Assert.Equal(
|
|
$"Unable to set property '{nameof(HasInstanceProperties.IntProp)}' on object of " +
|
|
$"type '{typeof(HasInstanceProperties).FullName}'. The error was: {ex.InnerException.Message}",
|
|
ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void PropertyExplicitSetterException_Throws()
|
|
{
|
|
// Arrange
|
|
var target = new HasPropertyWhoseSetterThrows();
|
|
var parameterCollection = new ParameterCollectionBuilder
|
|
{
|
|
{ nameof(HasPropertyWhoseSetterThrows.StringProp), "anything" },
|
|
}.Build();
|
|
|
|
// Act
|
|
var ex = Assert.Throws<InvalidOperationException>(
|
|
() => parameterCollection.AssignToProperties(target));
|
|
|
|
// Assert
|
|
Assert.Equal(
|
|
$"Unable to set property '{nameof(HasPropertyWhoseSetterThrows.StringProp)}' on object of " +
|
|
$"type '{typeof(HasPropertyWhoseSetterThrows).FullName}'. The error was: {ex.InnerException.Message}",
|
|
ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void DeclaredParametersVaryOnlyByCase_Throws()
|
|
{
|
|
// Arrange
|
|
var parameterCollection = new ParameterCollectionBuilder().Build();
|
|
var target = new HasParametersVaryingOnlyByCase();
|
|
|
|
// Act
|
|
var ex = Assert.Throws<InvalidOperationException>(() =>
|
|
parameterCollection.AssignToProperties(target));
|
|
|
|
// Assert
|
|
Assert.Equal(
|
|
$"The type '{typeof(HasParametersVaryingOnlyByCase).FullName}' declares more than one parameter matching the " +
|
|
$"name '{nameof(HasParametersVaryingOnlyByCase.MyValue).ToLowerInvariant()}'. Parameter names are case-insensitive and must be unique.",
|
|
ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void DeclaredParameterClashesWithInheritedParameter_Throws()
|
|
{
|
|
// Even when the developer uses 'new' to shadow an inherited property, this is not
|
|
// an allowed scenario because there would be no way for the consumer to specify
|
|
// both property values, and it's no good leaving the shadowed one unset because the
|
|
// base class can legitimately depend on it for correct functioning.
|
|
|
|
// Arrange
|
|
var parameterCollection = new ParameterCollectionBuilder().Build();
|
|
var target = new HasParameterClashingWithInherited();
|
|
|
|
// Act
|
|
var ex = Assert.Throws<InvalidOperationException>(() =>
|
|
parameterCollection.AssignToProperties(target));
|
|
|
|
// Assert
|
|
Assert.Equal(
|
|
$"The type '{typeof(HasParameterClashingWithInherited).FullName}' declares more than one parameter matching the " +
|
|
$"name '{nameof(HasParameterClashingWithInherited.IntProp).ToLowerInvariant()}'. Parameter names are case-insensitive and must be unique.",
|
|
ex.Message);
|
|
}
|
|
|
|
|
|
class HasInstanceProperties
|
|
{
|
|
// "internal" to show we're not requiring public accessors, but also
|
|
// to keep the assertions simple in the tests
|
|
|
|
[Parameter] internal int IntProp { get; set; }
|
|
[Parameter] internal string StringProp { get; set; }
|
|
|
|
// Also a truly private one to show there's nothing special about 'internal'
|
|
[Parameter] private object ObjectProp { get; set; }
|
|
|
|
public static string ObjectPropName => nameof(ObjectProp);
|
|
public object ObjectPropCurrentValue
|
|
{
|
|
get => ObjectProp;
|
|
set => ObjectProp = value;
|
|
}
|
|
}
|
|
|
|
class HasPropertyWithoutParameterAttribute
|
|
{
|
|
internal int IntProp { get; set; }
|
|
}
|
|
|
|
class HasPropertyWhoseSetterThrows
|
|
{
|
|
[Parameter]
|
|
internal string StringProp
|
|
{
|
|
get => string.Empty;
|
|
set => throw new InvalidOperationException("This setter throws");
|
|
}
|
|
}
|
|
|
|
class HasInheritedProperties : HasInstanceProperties
|
|
{
|
|
[Parameter] internal int DerivedClassIntProp { get; set; }
|
|
}
|
|
|
|
class HasParametersVaryingOnlyByCase
|
|
{
|
|
[Parameter] internal object MyValue { get; set; }
|
|
[Parameter] internal object Myvalue { get; set; }
|
|
}
|
|
|
|
class HasParameterClashingWithInherited : HasInstanceProperties
|
|
{
|
|
[Parameter] new int IntProp { get; set; }
|
|
}
|
|
|
|
class ParameterCollectionBuilder : IEnumerable
|
|
{
|
|
private readonly List<(string Name, object Value)> _keyValuePairs
|
|
= new List<(string, object)>();
|
|
|
|
public void Add(string name, object value)
|
|
=> _keyValuePairs.Add((name, value));
|
|
|
|
public IEnumerator GetEnumerator()
|
|
=> throw new NotImplementedException();
|
|
|
|
public ParameterCollection Build()
|
|
{
|
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
|
builder.OpenComponent<FakeComponent>(0);
|
|
foreach (var kvp in _keyValuePairs)
|
|
{
|
|
builder.AddAttribute(1, kvp.Name, kvp.Value);
|
|
}
|
|
builder.CloseComponent();
|
|
return new ParameterCollection(builder.GetFrames().Array, ownerIndex: 0);
|
|
}
|
|
}
|
|
|
|
class FakeComponent : IComponent
|
|
{
|
|
public void Init(RenderHandle renderHandle)
|
|
=> throw new NotImplementedException();
|
|
|
|
public void SetParameters(ParameterCollection parameters)
|
|
=> throw new NotImplementedException();
|
|
}
|
|
}
|
|
}
|