Skip to content
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
52 changes: 52 additions & 0 deletions playground/Stress/Stress.AppHost/InteractionCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,58 @@ public static IResourceBuilder<T> AddInteractionCommands<T>(this IResourceBuilde

return CommandResults.Success();
})
.WithCommand("choice-no-placeholder", "Choice with no placeholder", executeCommand: async commandContext =>
{
var interactionService = commandContext.ServiceProvider.GetRequiredService<IInteractionService>();
var dinnerInput = new InteractionInput
{
Name = "Dinner",
InputType = InputType.Choice,
Label = "Dinner",
Required = true,
Options =
[
KeyValuePair.Create("pizza", "Pizza"),
KeyValuePair.Create("fried-chicken", "Fried chicken"),
KeyValuePair.Create("burger", "Burger")
]
};
var requirementsInput = new InteractionInput
{
Name = "Requirements",
InputType = InputType.Choice,
Label = "Requirements",
AllowCustomChoice = true,
Options =
[
KeyValuePair.Create("vegetarian", "Vegetarian"),
KeyValuePair.Create("vegan", "Vegan")
]
};
var result = await interactionService.PromptInputsAsync(
title: "Text request",
message: "Provide your name",
inputs: [
dinnerInput,
requirementsInput
],
cancellationToken: commandContext.CancellationToken);

if (result.Canceled)
{
return CommandResults.Failure("Canceled");
}

var resourceLoggerService = commandContext.ServiceProvider.GetRequiredService<ResourceLoggerService>();
var logger = resourceLoggerService.GetLogger(commandContext.ResourceName);

foreach (var updatedInput in result.Data)
{
logger.LogInformation("Input: {Label} = {Value}", updatedInput.Label, updatedInput.Value);
}

return CommandResults.Success();
})
.WithCommand("input-interaction", "Input interactions", executeCommand: async commandContext =>
{
var interactionService = commandContext.ServiceProvider.GetRequiredService<IInteractionService>();
Expand Down
7 changes: 7 additions & 0 deletions src/Aspire.Dashboard/Model/Interaction/InputViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ public void SetInput(InteractionInput input)
.ToList();

SelectOptions = optionsVM;

// Default to the first option if no placeholder is set, the value is empty, and custom choice is disabled.
// This is done so the input model value matches frontend behavior (FluentSelect defaults to the first option)
if (string.IsNullOrEmpty(input.Placeholder) && string.IsNullOrEmpty(input.Value) && optionsVM.Count > 0 && !input.AllowCustomChoice)
{
input.Value = optionsVM[0].Id;
}
}
}

Expand Down
181 changes: 181 additions & 0 deletions tests/Aspire.Dashboard.Components.Tests/Model/InputViewModelTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Dashboard.Model.Interaction;
using Aspire.DashboardService.Proto.V1;
using Xunit;

namespace Aspire.Dashboard.Components.Tests.Model;

public class InputViewModelTests
{
[Fact]
public void InputViewModel_ChoiceWithoutPlaceholder_DefaultsToFirstOption()
{
// Arrange
var input = new InteractionInput
{
Label = "Choose Color",
InputType = InputType.Choice,
};
input.Options.Add("red", "Red");
input.Options.Add("blue", "Blue");
input.Options.Add("green", "Green");

// Act
var viewModel = new InputViewModel(input);

// Assert
Assert.Equal("red", viewModel.Value);
}

[Fact]
public void InputViewModel_ChoiceWithPlaceholder_DoesNotDefaultToFirstOption()
{
// Arrange
var input = new InteractionInput
{
Label = "Choose Color",
InputType = InputType.Choice,
Placeholder = "Select a color"
};
input.Options.Add("red", "Red");
input.Options.Add("blue", "Blue");
input.Options.Add("green", "Green");

// Act
var viewModel = new InputViewModel(input);

// Assert - proto strings default to empty string, not null
Assert.True(string.IsNullOrEmpty(viewModel.Value));
}

[Fact]
public void InputViewModel_ChoiceWithExistingValue_KeepsExistingValue()
{
// Arrange
var input = new InteractionInput
{
Label = "Choose Color",
InputType = InputType.Choice,
Value = "blue"
};
input.Options.Add("red", "Red");
input.Options.Add("blue", "Blue");
input.Options.Add("green", "Green");

// Act
var viewModel = new InputViewModel(input);

// Assert
Assert.Equal("blue", viewModel.Value);
}

[Fact]
public void InputViewModel_ChoiceWithEmptyOptions_DoesNotSetValue()
{
// Arrange
var input = new InteractionInput
{
Label = "Choose Color",
InputType = InputType.Choice
};

// Act
var viewModel = new InputViewModel(input);

// Assert - proto strings default to empty string, not null
Assert.True(string.IsNullOrEmpty(viewModel.Value));
}

[Fact]
public void InputViewModel_NonChoiceInput_DoesNotSetValue()
{
// Arrange
var input = new InteractionInput
{
Label = "Enter Text",
InputType = InputType.Text
};

// Act
var viewModel = new InputViewModel(input);

// Assert - proto strings default to empty string, not null
Assert.True(string.IsNullOrEmpty(viewModel.Value));
}

[Fact]
public void SetInput_ChoiceWithoutPlaceholder_DefaultsToFirstOption()
{
// Arrange
var initialInput = new InteractionInput
{
Label = "Enter Text",
InputType = InputType.Text
};
var viewModel = new InputViewModel(initialInput);

var choiceInput = new InteractionInput
{
Label = "Choose Color",
InputType = InputType.Choice
};
choiceInput.Options.Add("red", "Red");
choiceInput.Options.Add("blue", "Blue");

// Act
viewModel.SetInput(choiceInput);

// Assert
Assert.Equal("red", viewModel.Value);
}

[Fact]
public void SetInput_ChoiceWithPlaceholder_DoesNotDefaultToFirstOption()
{
// Arrange
var initialInput = new InteractionInput
{
Label = "Enter Text",
InputType = InputType.Text
};
var viewModel = new InputViewModel(initialInput);

var choiceInput = new InteractionInput
{
Label = "Choose Color",
InputType = InputType.Choice,
Placeholder = "Select a color"
};
choiceInput.Options.Add("red", "Red");
choiceInput.Options.Add("blue", "Blue");

// Act
viewModel.SetInput(choiceInput);

// Assert - proto strings default to empty string, not null
Assert.True(string.IsNullOrEmpty(viewModel.Value));
}

[Fact]
public void InputViewModel_ChoiceWithAllowCustomChoice_DoesNotDefaultToFirstOption()
{
// Arrange
var input = new InteractionInput
{
Label = "Choose Color",
InputType = InputType.Choice,
AllowCustomChoice = true
};
input.Options.Add("red", "Red");
input.Options.Add("blue", "Blue");
input.Options.Add("green", "Green");

// Act
var viewModel = new InputViewModel(input);

// Assert - When AllowCustomChoice is true, value should not default
Assert.True(string.IsNullOrEmpty(viewModel.Value));
}
}