diff --git a/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatMessageList.razor b/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatMessageList.razor
index 104d7ec8..96363130 100644
--- a/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatMessageList.razor
+++ b/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatMessageList.razor
@@ -1,5 +1,3 @@
-@inject IJSRuntime JS
-
@foreach (var message in Messages)
@@ -17,25 +15,3 @@
}
-
-@code {
- [Parameter]
- public required IEnumerable Messages { get; set; }
-
- [Parameter]
- public ChatMessage? InProgressMessage { get; set; }
-
- [Parameter]
- public RenderFragment? NoMessagesContent { get; set; }
-
- private bool IsEmpty => !Messages.Any(m => (m.Role == ChatRole.User || m.Role == ChatRole.Assistant) && !string.IsNullOrEmpty(m.Text));
-
- protected override async Task OnAfterRenderAsync(bool firstRender)
- {
- if (firstRender)
- {
- // Activates the auto-scrolling behavior
- await JS.InvokeVoidAsync("import", "./Components/Pages/Chat/ChatMessageList.razor.js");
- }
- }
-}
diff --git a/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatMessageList.razor.cs b/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatMessageList.razor.cs
new file mode 100644
index 00000000..1fcf88f1
--- /dev/null
+++ b/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatMessageList.razor.cs
@@ -0,0 +1,30 @@
+using Microsoft.AspNetCore.Components;
+using Microsoft.Extensions.AI;
+using Microsoft.JSInterop;
+
+namespace OpenChat.PlaygroundApp.Components.Pages.Chat;
+
+public partial class ChatMessageList : ComponentBase
+{
+ [Inject]
+ public IJSRuntime? JS { get; set; }
+
+ [Parameter]
+ public required IEnumerable Messages { get; set; }
+
+ [Parameter]
+ public ChatMessage? InProgressMessage { get; set; }
+
+ [Parameter]
+ public RenderFragment? NoMessagesContent { get; set; }
+
+ private bool IsEmpty => !Messages.Any(m => (m.Role == ChatRole.User || m.Role == ChatRole.Assistant) && !string.IsNullOrEmpty(m.Text));
+
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ if (firstRender)
+ {
+ await JS!.InvokeVoidAsync("import", "./Components/Pages/Chat/ChatMessageList.razor.js");
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/OpenChat.PlaygroundApp.Tests/Components/Pages/Chat/ChatMessageListUITests.cs b/test/OpenChat.PlaygroundApp.Tests/Components/Pages/Chat/ChatMessageListUITests.cs
new file mode 100644
index 00000000..6d4fcde8
--- /dev/null
+++ b/test/OpenChat.PlaygroundApp.Tests/Components/Pages/Chat/ChatMessageListUITests.cs
@@ -0,0 +1,129 @@
+using Microsoft.Playwright;
+using Microsoft.Playwright.Xunit;
+
+namespace OpenChat.PlaygroundApp.Tests.Components.Pages.Chat;
+
+public class ChatMessageListUITests : PageTest
+{
+ public override async Task InitializeAsync()
+ {
+ await base.InitializeAsync();
+ await Page.GotoAsync(TestConstants.LocalhostUrl);
+ await Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
+ }
+
+ [Trait("Category", "IntegrationTest")]
+ [Theory]
+ [InlineData("To get started, try asking about anything.")]
+ public async Task Given_EmptyState_When_Page_Loads_Then_NoMessages_Content_Should_Be_Visible(string expectedText)
+ {
+ // Act
+ var noMessagesElement = Page.Locator(".no-messages");
+
+ // Assert
+ var isVisible = await noMessagesElement.IsVisibleAsync();
+ isVisible.ShouldBeTrue();
+
+ var content = await noMessagesElement.InnerTextAsync();
+ content.ShouldContain(expectedText);
+ }
+
+ [Trait("Category", "IntegrationTest")]
+ [Theory]
+ [InlineData("Hello, how are you?")]
+ public async Task Given_MessagesExist_When_Rendered_Then_MessageList_Should_Display_Messages(string userMessage)
+ {
+ // Arrange
+ var textArea = Page.GetByRole(AriaRole.Textbox, new() { Name = "User Message Textarea" });
+ await textArea.FillAsync(userMessage);
+ await textArea.PressAsync("Enter");
+ await Page.Locator(".user-message").First.WaitForAsync(new() { State = WaitForSelectorState.Attached });
+
+ // Act
+ var messageListContainer = Page.Locator(".message-list-container");
+ var messageListContent = await messageListContainer.InnerTextAsync();
+
+ // Assert
+ messageListContent.ShouldContain(userMessage);
+ }
+
+ [Trait("Category", "IntegrationTest")]
+ [Fact]
+ public async Task Given_InitialState_When_NoUserInteraction_Then_InProgressMessage_Should_Be_Null()
+ {
+ // Act
+ var chatMessages = Page.Locator("chat-messages");
+ var inProgressAttribute = await chatMessages.GetAttributeAsync("in-progress");
+
+ // Assert
+ inProgressAttribute.ShouldBeNull();
+ }
+
+ [Trait("Category", "IntegrationTest")]
+ [Theory]
+ [InlineData("Test message for container")]
+ [InlineData("Another message for verification")]
+ public async Task Given_BasicTextInput_When_NoMessageSending_Then_ContainerStructure_And_InputFunction_Should_Work(string userMessage)
+ {
+ // Arrange
+ var textArea = Page.GetByRole(AriaRole.Textbox, new() { Name = "User Message Textarea" });
+ var messageContainer = Page.Locator(".message-list-container");
+
+ // Act
+ await textArea.FillAsync(userMessage);
+ var containerExists = await messageContainer.IsVisibleAsync();
+ var inputValue = await textArea.InputValueAsync();
+
+ // Assert
+ containerExists.ShouldBeTrue();
+ inputValue.ShouldBe(userMessage);
+ }
+
+ [Trait("Category", "IntegrationTest")]
+ [Theory]
+ [InlineData(".message-list-container")]
+ [InlineData("chat-messages")]
+ public async Task Given_ChatMessageList_When_Rendered_Then_Should_Display_Container_Structure(string containerSelector)
+ {
+ // Act
+ var container = Page.Locator(containerSelector);
+ var containerExists = await container.IsVisibleAsync();
+
+ // Assert
+ containerExists.ShouldBeTrue();
+ }
+
+ [Trait("Category", "IntegrationTest")]
+ [Theory]
+ [InlineData("To get started, try asking about anything.")]
+ public async Task Given_EmptyState_When_NoMessages_Then_NoMessagesContent_Should_Be_Visible(string expectedText)
+ {
+ // Act
+ var noMessagesElement = Page.Locator(".no-messages");
+ var isVisible = await noMessagesElement.IsVisibleAsync();
+ var content = await noMessagesElement.InnerTextAsync();
+
+ // Assert
+ isVisible.ShouldBeTrue();
+ content.ShouldContain(expectedText);
+ }
+
+ [Trait("Category", "IntegrationTest")]
+ [Theory]
+ [InlineData("User Message Textarea")]
+ public async Task Given_EmptyState_When_NoMessages_Then_InputField_Should_Be_Visible(string inputFieldName)
+ {
+ // Act
+ var textArea = Page.GetByRole(AriaRole.Textbox, new() { Name = inputFieldName });
+ var inputExists = await textArea.IsVisibleAsync();
+
+ // Assert
+ inputExists.ShouldBeTrue();
+ }
+
+ public override async Task DisposeAsync()
+ {
+ await Page.CloseAsync();
+ await base.DisposeAsync();
+ }
+}
\ No newline at end of file