// 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.Generic; using System.Linq.Expressions; using Xunit; namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal { public class ExpressionHelperTest { private readonly ExpressionTextCache _expressionTextCache = new ExpressionTextCache(); public static IEnumerable ExpressionAndTexts { get { var i = 3; var value = "Test"; var Model = new TestModel(); var key = "TestModel"; var myModels = new List(); return new TheoryData { { (Expression>)(model => model.SelectedCategory), "SelectedCategory" }, { (Expression>)(m => Model.SelectedCategory), "SelectedCategory" }, { (Expression>)(model => model.SelectedCategory.CategoryName), "SelectedCategory.CategoryName" }, { (Expression>)(testModel => testModel.SelectedCategory.CategoryId), "SelectedCategory.CategoryId" }, { (Expression>)(model => model.SelectedCategory.CategoryName.MainCategory), "SelectedCategory.CategoryName.MainCategory" }, { (Expression>)(model => model), string.Empty }, { (Expression>)(model => value), "value" }, { (Expression>)(m => Model), string.Empty }, { (Expression, Category>>)(model => model[2].SelectedCategory), "[2].SelectedCategory" }, { (Expression, Category>>)(model => model[i].SelectedCategory), "[3].SelectedCategory" }, { (Expression, string>>)(model => model[key].SelectedCategory.CategoryName.MainCategory), "[TestModel].SelectedCategory.CategoryName.MainCategory" }, { (Expression>)(model => model.PreferredCategories[i].CategoryId), "PreferredCategories[3].CategoryId" }, { (Expression, Category>>)(model => myModels[i].SelectedCategory), "myModels[3].SelectedCategory" }, { (Expression, int>>)(model => model[2].PreferredCategories[i].CategoryId), "[2].PreferredCategories[3].CategoryId" }, }; } } public static IEnumerable CachedExpressions { get { var key = "TestModel"; var myModel = new TestModel(); return new TheoryData { (Expression>)(model => model.SelectedCategory), (Expression>)(model => model.SelectedCategory.CategoryName), (Expression>)(testModel => testModel.SelectedCategory.CategoryId), (Expression>)(model => model.SelectedCategory.CategoryName.MainCategory), (Expression>)(testModel => key), (Expression>)(m => m), (Expression>)(m => myModel.SelectedCategory), }; } } public static IEnumerable IndexerExpressions { get { var i = 3; var key = "TestModel"; var myModels = new List(); return new TheoryData { (Expression, Category>>)(model => model[2].SelectedCategory), (Expression, Category>>)(model => myModels[i].SelectedCategory), (Expression, CategoryName>>)(testModel => testModel[i].SelectedCategory.CategoryName), (Expression>)(model => model.PreferredCategories[i].CategoryId), (Expression, string>>)(model => model[key].SelectedCategory.CategoryName.MainCategory), }; } } public static IEnumerable EquivalentExpressions { get { var value = "Test"; var Model = "Test"; return new TheoryData { { (Expression>)(model => model.SelectedCategory), (Expression>)(model => model.SelectedCategory) }, { (Expression>)(model => model.SelectedCategory.CategoryName), (Expression>)(model => model.SelectedCategory.CategoryName) }, { (Expression>)(testModel => testModel.SelectedCategory.CategoryId), (Expression>)(testModel => testModel.SelectedCategory.CategoryId) }, { (Expression>)(model => model.SelectedCategory.CategoryName.MainCategory), (Expression>)(model => model.SelectedCategory.CategoryName.MainCategory) }, { (Expression>)(model => model), (Expression>)(m => m) }, { (Expression>)(model => value), (Expression>)(m => value) }, { // These two expressions are not actually equivalent. However ExpressionHelper returns // string.Empty for these two expressions and hence they are considered as equivalent by the // cache. (Expression>)(m => Model), (Expression>)(m => m) }, }; } } public static IEnumerable NonEquivalentExpressions { get { var value = "test"; var key = "TestModel"; var Model = "Test"; var myModel = new TestModel(); return new TheoryData { { (Expression>)(model => model.SelectedCategory), (Expression>)(model => model.SelectedCategory.CategoryName) }, { (Expression>)(model => model.Model), (Expression>)(model => model.Name) }, { (Expression>)(model => model.SelectedCategory.CategoryName), (Expression>)(model => value) }, { (Expression>)(testModel => testModel.SelectedCategory.CategoryName.MainCategory), (Expression>)(testModel => value) }, { (Expression, Category>>)(model => model[2].SelectedCategory), (Expression>)(model => model.SelectedCategory.CategoryName.MainCategory) }, { (Expression>)(testModel => testModel.SelectedCategory.CategoryId), (Expression>)(model => model.SelectedCategory) }, { (Expression, string>>)(model => model[key].SelectedCategory.CategoryName.MainCategory), (Expression>)(model => model.SelectedCategory) }, { (Expression>)(m => Model), (Expression>)(m => m.Model) }, { (Expression>)(m => m), (Expression>)(m => m.Model) }, { (Expression>)(m => myModel.Name), (Expression>)(m => m.Name) }, { (Expression>)(m => key), (Expression>)(m => value) }, }; } } [Theory] [MemberData(nameof(ExpressionAndTexts))] public void GetExpressionText_ReturnsExpectedExpressionText(LambdaExpression expression, string expressionText) { // Act var text = ExpressionHelper.GetExpressionText(expression, _expressionTextCache); // Assert Assert.Equal(expressionText, text); } [Theory] [MemberData(nameof(CachedExpressions))] public void GetExpressionText_CachesExpression(LambdaExpression expression) { // Act - 1 var text1 = ExpressionHelper.GetExpressionText(expression, _expressionTextCache); // Act - 2 var text2 = ExpressionHelper.GetExpressionText(expression, _expressionTextCache); // Assert Assert.Same(text1, text2); // cached } [Theory] [MemberData(nameof(IndexerExpressions))] public void GetExpressionText_DoesNotCacheIndexerExpression(LambdaExpression expression) { // Act - 1 var text1 = ExpressionHelper.GetExpressionText(expression, _expressionTextCache); // Act - 2 var text2 = ExpressionHelper.GetExpressionText(expression, _expressionTextCache); // Assert Assert.Equal(text1, text2, StringComparer.Ordinal); Assert.NotSame(text1, text2); // not cached } [Theory] [MemberData(nameof(EquivalentExpressions))] public void GetExpressionText_CacheEquivalentExpressions(LambdaExpression expression1, LambdaExpression expression2) { // Act - 1 var text1 = ExpressionHelper.GetExpressionText(expression1, _expressionTextCache); // Act - 2 var text2 = ExpressionHelper.GetExpressionText(expression2, _expressionTextCache); // Assert Assert.Same(text1, text2); // cached } [Theory] [MemberData(nameof(NonEquivalentExpressions))] public void GetExpressionText_CheckNonEquivalentExpressions(LambdaExpression expression1, LambdaExpression expression2) { // Act - 1 var text1 = ExpressionHelper.GetExpressionText(expression1, _expressionTextCache); // Act - 2 var text2 = ExpressionHelper.GetExpressionText(expression2, _expressionTextCache); // Assert Assert.NotEqual(text1, text2, StringComparer.Ordinal); Assert.NotSame(text1, text2); } private class TestModel { public string Name { get; set; } public string Model { get; set; } public Category SelectedCategory { get; set; } public IList PreferredCategories { get; set; } } private class Category { public int CategoryId { get; set; } public CategoryName CategoryName { get; set; } } private class CategoryName { public string MainCategory { get; set; } public string SubCategory { get; set; } } } }