Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4714dd8
refactor: split UI logic in ChatHeader.razor
qoweh Sep 13, 2025
04735cc
test: add integration tests for NewChat button and icon visibility
qoweh Sep 13, 2025
a8f0a7d
Merge remote-tracking branch 'upstream/main'
qoweh Sep 13, 2025
6e68171
Merge remote-tracking branch 'origin/main'
qoweh Sep 18, 2025
1f5783c
Merge branch 'aliencube:main' into main
qoweh Sep 22, 2025
5094a9f
Merge branch 'aliencube:main' into main
qoweh Sep 23, 2025
8ed4bfd
Merge branch 'aliencube:main' into main
qoweh Sep 25, 2025
39354bc
Merge branch 'aliencube:main' into main
qoweh Sep 28, 2025
d2aab18
Merge remote-tracking branch 'aliencube/main' into feat/301-UICompone…
qoweh Sep 29, 2025
0390aed
refactor: Move ChatMessageItem logic to separate code-behind file
qoweh Sep 29, 2025
b3db7d8
test: Add integration tests for ChatMessageItem component
qoweh Sep 29, 2025
1588a7b
refactor: Rename test method for clarity and reintroduce assistant ic…
qoweh Sep 29, 2025
1569fc3
refactor: Improve readability of assistant message visibility check i…
qoweh Sep 29, 2025
12d19ab
Merge remote-tracking branch 'aliencube/main' into feat/301-UICompone…
qoweh Sep 29, 2025
1ba2589
Merge remote-tracking branch 'aliencube/main' into feat/301-UICompone…
qoweh Sep 30, 2025
4636787
fix: Subtract ChatHeaderUITests from commit
qoweh Oct 1, 2025
931f917
refactor: for improved reliability in ChatMessageItemUITests
qoweh Oct 1, 2025
46095a2
refactor: remove unused using directive for improved code clarity
qoweh Oct 1, 2025
87953f9
fix: finalContent in ChatMessageItemUITests
qoweh Oct 1, 2025
ad026c6
refactor: remove unused directive and empty line
qoweh Oct 1, 2025
d4b0b73
Merge remote-tracking branch 'aliencube/main' into feat/301-UICompone…
qoweh Oct 5, 2025
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
@using System.Linq
@using System.Runtime.CompilerServices
@using System.Text.RegularExpressions

@if (Message.Role == ChatRole.User)
{
<div class="user-message">
Expand Down Expand Up @@ -34,26 +30,3 @@ else if (Message.Role == ChatRole.Assistant)
}
}
}

@code {
private static readonly ConditionalWeakTable<ChatMessage, ChatMessageItem> SubscribersLookup = new();

[Parameter, EditorRequired]
public required ChatMessage Message { get; set; }

[Parameter]
public bool InProgress { get; set;}

protected override void OnInitialized()
{
SubscribersLookup.AddOrUpdate(Message, this);
}

public static void NotifyChanged(ChatMessage source)
{
if (SubscribersLookup.TryGetValue(source, out var subscriber))
{
subscriber.StateHasChanged();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.AI;

namespace OpenChat.PlaygroundApp.Components.Pages.Chat;

public partial class ChatMessageItem : ComponentBase
{
private static readonly ConditionalWeakTable<ChatMessage, ChatMessageItem> SubscribersLookup = new();

[Parameter, EditorRequired]
public required ChatMessage Message { get; set; }

[Parameter]
public bool InProgress { get; set; }

protected override void OnInitialized()
{
SubscribersLookup.AddOrUpdate(Message, this);
}

public static void NotifyChanged(ChatMessage source)
{
if (SubscribersLookup.TryGetValue(source, out var subscriber))
{
subscriber.StateHasChanged();
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 파일은 왜 수정하신 거죠?

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.Playwright;
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;

namespace OpenChat.PlaygroundApp.Tests.Components.Pages.Chat;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using System;
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;

namespace OpenChat.PlaygroundApp.Tests.Components.Pages.Chat;

public class ChatMessageItemUITests : PageTest
{
public override async Task InitializeAsync()
{
await base.InitializeAsync();
await Page.GotoAsync(TestConstants.LocalhostUrl);
await Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
}

[Trait("Category", "IntegrationTest")]
[Theory]
[InlineData("Input usermessage")]
public async Task Given_UserMessage_When_Sent_Then_UserMessage_Count_Should_Increment(string userMessage)
{
// Arrange
var textArea = Page.GetByRole(AriaRole.Textbox, new() { Name = "User Message Textarea" });
var userMessages = Page.Locator(".user-message");
var initialCount = await userMessages.CountAsync();

// Act
await textArea.FillAsync(userMessage);
await textArea.PressAsync("Enter");
await Page.WaitForFunctionAsync(
"args => document.querySelectorAll(args.selector).length >= args.expected",
new { selector = ".user-message", expected = initialCount + 1 }
);

// Assert
var finalCount = await userMessages.CountAsync();
finalCount.ShouldBe(initialCount + 1);
}

[Trait("Category", "IntegrationTest")]
[Theory]
[InlineData("Input usermessage")]
public async Task Given_UserMessage_When_Sent_Then_Rendered_Text_Should_Match_UserMessage(string userMessage)
{
// Arrange
var textArea = Page.GetByRole(AriaRole.Textbox, new() { Name = "User Message Textarea" });
var userMessages = Page.Locator(".user-message");
var initialCount = await userMessages.CountAsync();

// Act
await textArea.FillAsync(userMessage);
await textArea.PressAsync("Enter");
await Page.WaitForFunctionAsync(
"args => document.querySelectorAll(args.selector).length >= args.expected",
new { selector = ".user-message", expected = initialCount + 1 }
);

// Assert
var latestUserMessage = userMessages.Nth(initialCount);
var renderedText = await latestUserMessage.InnerTextAsync();
renderedText.ShouldBe(userMessage);
}

[Trait("Category", "IntegrationTest")]
[Trait("Category", "LLMRequired")]
[Theory]
[InlineData("Input usermessage")]
public async Task Given_AssistantResponse_When_Streamed_Then_Latest_Text_Should_NotBeEmpty(string userMessage)
{
// Arrange
var textArea = Page.GetByRole(AriaRole.Textbox, new() { Name = "User Message Textarea" });
var assistantTexts = Page.Locator(".assistant-message-text");
var initialTextCount = await assistantTexts.CountAsync();

// Act
await textArea.FillAsync(userMessage);
await textArea.PressAsync("Enter");
await Page.WaitForFunctionAsync(
"args => document.querySelectorAll(args.selector).length >= args.expected",
new { selector = ".assistant-message-text", expected = initialTextCount + 1 }
);
await Page.WaitForFunctionAsync(
@"selector =>
{
const elements = document.querySelectorAll(selector);
const latest = elements[elements.length - 1];
if (latest == null)
{
return false;
}

return latest.innerText?.trim().length > 0;
}",
".assistant-message-text"
);

// Assert
var latestAssistantText = assistantTexts.Nth(initialTextCount);
var finalContent = await latestAssistantText.InnerTextAsync();
finalContent.ShouldNotBeNullOrWhiteSpace();
}

[Trait("Category", "IntegrationTest")]
[Trait("Category", "LLMRequired")]
[Theory]
[InlineData("Input usermessage")]
public async Task Given_AssistantResponse_When_Message_Arrives_Then_Assistant_Icon_Should_Be_Visible(string userMessage)
{
// Arrange
var textArea = Page.GetByRole(AriaRole.Textbox, new() { Name = "User Message Textarea" });
var assistantHeaders = Page.Locator(".assistant-message-header");
var assistantIcons = Page.Locator(".assistant-message-icon svg");
var initialHeaderCount = await assistantHeaders.CountAsync();

// Act
await textArea.FillAsync(userMessage);
await textArea.PressAsync("Enter");
await Page.WaitForFunctionAsync(
"args => document.querySelectorAll(args.selector).length >= args.expected",
new { selector = ".assistant-message-header", expected = initialHeaderCount + 1 }
);

// Assert
var finalIconCount = await assistantIcons.CountAsync();
var iconIndex = finalIconCount - 1;
var iconVisible = await assistantIcons.Nth(iconIndex).IsVisibleAsync();
iconVisible.ShouldBeTrue();
}

[Trait("Category", "IntegrationTest")]
[Trait("Category", "LLMRequired")]
[Theory]
[InlineData("Input usermessage")]
public async Task Given_Response_InProgress_When_Stream_Completes_Then_Spinner_Should_Toggle(string userMessage)
{
// Arrange
var textArea = Page.GetByRole(AriaRole.Textbox, new() { Name = "User Message Textarea" });
var spinner = Page.Locator(".lds-ellipsis");

// Act
await textArea.FillAsync(userMessage);
await textArea.PressAsync("Enter");
await spinner.WaitForAsync(new() { State = WaitForSelectorState.Visible });
var visibleWhileStreaming = await spinner.IsVisibleAsync();
await spinner.WaitForAsync(new() { State = WaitForSelectorState.Hidden });
var visibleAfterComplete = await spinner.IsVisibleAsync();

// Assert
visibleWhileStreaming.ShouldBeTrue();
visibleAfterComplete.ShouldBeFalse();
}

public override async Task DisposeAsync()
{
await Page.CloseAsync();
await base.DisposeAsync();
}
}